Error Handling
Custom 404 and 500 pages with special files.
Two special files at the root of app/ control the non-happy paths:
- not-found.tsx - rendered with status 404 for unmatched routes
- error.tsx - rendered with status 500 when a page render throws
Both render through your normal layout pipeline (root layout included), server-side. If neither exists, GioJS serves clean built-in pages instead.
// app/not-found.tsx
export default function NotFound() {
return <div><h1>404</h1><p>Page not found.</p></div>;
}The error page receives the failure via props. The message is worth showing only in development:
// app/error.tsx
export default function Error({ error }: { error?: { message: string } }) {
return (
<div>
<h1>Something went wrong</h1>
{process.env.NODE_ENV === 'development' && <pre>{error?.message}</pre>}
</div>
);
}Development error overlay
In development, SSR render errors - plus browser window errors and unhandled promise rejections - open a full-screen overlay instead of a bare 500. The overlay parses the stack, and for the topmost frame in your project it shows a codeframe: the failing line with four lines of context on each side, fetched from the dev-only /_gio/devtools/codeframe endpoint (reads are confined to source files inside the project root).
Every file:line in the stack is a link - clicking it opens the file at that line in your editor via /_gio/devtools/open-in-editor. The editor command comes from the first non-empty of GIO_EDITOR, VISUAL, EDITOR, defaulting to code; VS Code-family editors (code, cursor, windsurf, codium) get the -g file:line goto form, everything else a plain file:line argument.
# examples
GIO_EDITOR=cursor npm run dev
GIO_EDITOR="subl -w" npm run devStatic export
gio export writes your 404 page (or the built-in default) to out/404.html, which static hosts like Cloudflare Pages, GitHub Pages, and Netlify serve with a real 404 status for unknown URLs - without it, many hosts fall back to the home page with a 200.
API routes
Errors thrown in route.ts handlers are logged server-side and answered with a JSON 500 - internal details never reach the client. Requests for methods a handler file doesn't export get 405 with an Allow header.