TypeScript
Set up TypeScript in a GioJS app, type routes from your own folders with .gio/routes.d.ts, and find every type @gio.js/core and @gio.js/react export.
import type { PageProps } from '@gio.js/core';
export default function Post({ params }: PageProps<'/posts/:id'>) {
return <h1>Post {params.id}</h1>; // params: { id: string }
}GioJS runs TypeScript as is: the worker loads .ts and .tsx through tsx and bundles client code with esbuild, so there is no compile step and type errors never stop the server. Checking types is the job of tsc --noEmit, in your editor and in CI.
tsconfig.json
The TypeScript starter ships this file:
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"paths": {
"@/*": ["./*"]
}
},
"include": ["app", "components", ".gio/routes.d.ts"]
}".gio/routes.d.ts"inincludeturns on typed routes and the types of CSS imports.gio doctorwarns when it is missing.moduleResolution: "Bundler"matches how esbuild resolves imports, and lets TypeScript read the packages'exportsmaps (@gio.js/core/testing,@gio.js/core/server-only).pathsandjsxsettings are honored on the server and when bundling: the worker, the client bundles and standalone bundles all use the project'stsconfig.json(orjsconfig.json), even when the server starts from another directory.
Add a script so CI checks types. Run gio typegen first so the route types exist on a fresh checkout:
{
"scripts": {
"typecheck": "gio typegen && tsc --noEmit"
}
}The JavaScript starter (create-giojs --js) uses a jsconfig.json with checkJs: false and types its exports through JSDoc: /** @type {import('@gio.js/core').GetServerSideProps<{ post: Post }, '/posts/:id'>} */.
Typed routes
GioJS writes .gio/routes.d.ts from your app/ folder: one entry per page and route.ts, keyed by its route pattern, in the global GioJS.RegisteredRoutes interface. href(), useParams() and every route-typed type of @gio.js/core read it, so a pattern that is not one of your routes fails tsc, editors autocomplete patterns, and params need no annotations.
/// <reference path="./css-modules.d.ts" />
declare global {
namespace GioJS {
interface RegisteredRoutes {
'/': Record<string, never>;
'/api/posts/:id': { id: string };
'/blog/:slug': { slug: string };
'/docs/*slug': { slug: string };
'/shop/*path?': { path?: string };
}
}
}
export {};The file is rewritten at every server start (gio dev restarts on file changes, so it stays current) and by gio typegen, which needs no server and no secrets: a route.ts that fails to import is still typed. It is generated output; keep .gio/ out of git.
Route patterns
| Folder | Pattern | Params |
|---|---|---|
app/blog/[slug]/ | /blog/:slug | { slug: string } |
app/docs/[...slug]/ | /docs/*slug | { slug: string } - one string, 'a/b' |
app/shop/[[...path]]/ | /shop/*path? | { path?: string } - '' at runtime for /shop |
app/(marketing)/about/ | /about | Record<string, never> |
href
href(pattern, params) from @gio.js/react builds a URL from a registered pattern, encoding each param (a catch-all keeps its slashes). Static routes take no second argument, and an optional catch-all may leave it out:
import { href } from '@gio.js/react';
href('/blog/:slug', { slug: 'hello world' }); // '/blog/hello%20world'
href('/docs/*slug', { slug: 'a/b c' }); // '/docs/a/b%20c'
href('/shop/*path?'); // '/shop'
href('/blgo/:slug', { slug: 'x' }); // tsc: not assignable to '/' | '/blog/:slug' | ...ParamsOf, RouteParamsOf, ParamsFromPattern, StaticParamsOf
Every route-typed generic of @gio.js/core (PageProps, GetServerSideProps, GsspContext, RouteHandler, GioRequest, ActionArgs, GenerateMetadata, GetStaticPaths) takes a RouteOrParams: a pattern ('/blog/:slug') or a params shape ({ slug: string }). These helpers do the resolving and are exported for your own generics:
| Type | Description |
|---|---|
RoutePattern | The registered patterns once .gio/routes.d.ts is included, any string before that. |
RouteOrParams | What the route-typed generics accept: RoutePattern | object. |
ParamsOf<Route> | The params of a pattern or, given a params shape, that shape. |
RouteParamsOf<Pattern> | From the registry when the pattern is registered, else parsed from the pattern. |
ParamsFromPattern<Pattern> | Params parsed from the pattern string alone: :name and *name are strings, *name? is optional. |
StaticParamsOf<Route> | The params of one getStaticPaths entry: like ParamsOf, but a catch-all may also be an array of segments. |
Before .gio/routes.d.ts exists, these accept any pattern and parse its params from the string, so the @gio.js/core types work on a fresh checkout. href() and useParams<pattern>() from @gio.js/react do the same: href('/blog/:slug', { slug }) typechecks before the first server start, and still requires slug.
GioRegisteredRoutes
@gio.js/react's GioRegisteredRoutes extends GioJS.RegisteredRoutes. Routes you added to it by hand still type href(), but not the @gio.js/core types; declare extra routes on the global registry instead:
declare global {
namespace GioJS {
interface RegisteredRoutes {
'/legacy/:id': { id: string };
}
}
}
export {};CSS Module types
.gio/routes.d.ts references .gio/css-modules.d.ts, which types import styles from './card.module.css' as { readonly [className: string]: string } and a plain import './globals.css' as a side-effect import. No setup beyond the include entry.
Reference
Every type in these tables is a type-only export: import it with import type. The packages ship declaration files, so tsc --noEmit checks against them without compiling the framework. @gio.js/core also exports three classes, values you can use as types too: GioEventStream, and the MalformedBodyError and UnsupportedMediaTypeError that req.formData() and req.json() throw.
Pages and layouts
| Type | Description |
|---|---|
PageProps<Route> | Props of a page without getServerSideProps: params and searchParams. |
LayoutProps | children, and path: the page's path without query. |
ErrorPageProps | Props of error.tsx: { error: { message, digest? }, reset? }. Same as GioErrorProps. |
GioErrorProps, GioErrorInfo | The error boundary's props and its error; message is Internal Server Error in production. |
NotFoundPageProps | not-found.tsx renders with no props. |
Data fetching
| Type | Description |
|---|---|
GetServerSideProps<Props, Route> | A page's getServerSideProps; types ctx.params and the result. |
GetServerSidePropsContext<Route>, GsspContext<Route> | Its context: method, path, params, query, headers, cookies, locale, ip, scheme, host, requestId, actionData. |
GetServerSidePropsResult<Props> | PropsResult | RedirectResult | NotFoundResult | ActionRedirect. |
PropsResult<Props> | { props, headers?, tags? }. |
RedirectResult | { redirect: { destination, permanent }, headers? }. |
NotFoundResult | { notFound: true }, same as calling notFound(). |
GsspResponseHeaders | Record<string, string | string[]>; an array sends one header per value (set-cookie). |
InferPageProps<typeof getServerSideProps> | The props the page renders with, read off its getServerSideProps. |
GetStaticPaths<Route>, StaticPathsResult<Route> | getStaticPaths for gio export: { paths: [{ params }] }. |
Actions and forms
| Type | Description |
|---|---|
ActionArgs<Route> | A page action's request: the route-handler request with typed params. |
ActionResult<Data> | What an action may return: a Response, redirect(), { status, data, headers }, plain data or nothing. |
ActionDataResult<Data> | The { status?, data, headers? } form. |
ActionData<typeof action> | The actionData the page receives (responses and redirects left out, nothing becomes null). |
WithActionData<typeof action, Props> | Props plus an optional actionData. |
PageAction | The type of a page module's action export. |
ActionRedirect, RedirectInit | What redirect(url, init) returns, and its second argument (status, headers). |
GioFormProps, GioFormResult, GioFormState | From @gio.js/react: the <GioForm> props, the result its callbacks get, and useGioFormState()'s { pending, lastResult }. |
Route handlers and realtime
| Type | Description |
|---|---|
RouteHandler<Route> | A route.ts method handler: (req: GioRequest<Route>) => unknown. |
RouteHandlerFn | The untyped form the router calls. |
GioRequest<Route> | The request: method, path, params, query, headers, cookies, body, bodyBase64, json(), formData(), ip, scheme, host, requestId, locale. |
SseHandler, SseStream, SseCleanupFn | The callback a GioEventStream runs, the stream it writes to (send, close), and the cleanup function it may return - or resolve to, when it is async. |
WsHandler, GioSocket | A route.ts wsHandler and the socket it gets (send, close, join, leave, on, params, cookies, ...). |
BroadcastOptions | broadcast()'s options: { except?: socketId }. |
UseWebSocketOptions, UseWebSocketResult, ReconnectOptions, WebSocketData | From @gio.js/react: useWebSocket()'s options, result, backoff settings and message type. |
Metadata
| Type | Description |
|---|---|
Metadata | A page or layout's metadata: title, description, openGraph, twitter, alternates, robots, icons, themeColor, other and more. |
GenerateMetadata<Route>, MetadataContext<Route>, MetadataExtras | generateMetadata(ctx, { props }) and its two arguments. |
TitleTemplate | { default?, template?, absolute? }. |
OpenGraphMetadata, OpenGraphImage, TwitterMetadata, TwitterImage | Social cards. |
AlternatesMetadata, RobotsMetadata, RobotsDirectives | Canonical and language URLs, and robots directives. |
IconsMetadata, IconDescriptor, ThemeColorDescriptor, MetadataAuthor | Icons, theme colors and authors. |
MetadataRoute.Sitemap, MetadataRoute.Robots, MetadataRoute.Manifest | Return types of app/sitemap.ts, app/robots.ts and app/manifest.ts. |
Sitemap, SitemapEntry, ChangeFrequency, Robots, RobotsRule, Manifest | The same, by their own names. |
JsonLdProps, JsonLdData | From @gio.js/react: <JsonLd>'s props. |
Middleware, config and plugins
| Type | Description |
|---|---|
MiddlewareRules | What defineMiddleware() takes: redirects, rewrites, headers, guards. |
MiddlewareRedirect, MiddlewareRewrite, MiddlewareHeaderRule, MiddlewareGuard | One rule of each kind. |
GioConfig | gio.config.ts's shape (plugins), what defineConfig() takes. |
GioNodePlugin | A Node plugin: name, version, onRequest, onResponse, onStartup, onShutdown. |
IPCRequest, IPCResponse | The request and response a plugin's hooks see. |
Sessions, cookies and revalidation
| Type | Description |
|---|---|
SessionStorage<Data>, SessionStorageOptions | createSessionStorage()'s result and options. |
Session<Data>, SessionData, SessionSource, CommitSessionOptions | A session, its data, what getSession() reads from (a request, a context, a socket or a cookie header), and commitSession()'s options. |
CookieOptions | serializeCookie()'s options, with secure defaults. |
RevalidateResult, RevalidatePathOptions | { ok, purged, error? } from revalidateTag / revalidatePath, and { type?: 'page' | 'prefix' }. |
Client router and components
| Type | Description |
|---|---|
GioRouter | useRouter(): push, replace, back, forward, refresh, prefetch. |
NavigateOptions, RouterNavigateOptions | Options of navigate() (replace, scroll, transition) and of router.push / replace (the same without replace). |
ReadonlyURLSearchParams | useSearchParams(): URLSearchParams without the mutating methods. |
TransitionPreset, AnimatePreset | Names of the view-transition and <Animate> presets. |
GioRegisteredRoutes, RouteParamsOf, RoutePattern | The route registry as @gio.js/react sees it, a pattern's params (registered, or parsed from the pattern), and the patterns href() accepts. |
Testing
| Type | Description |
|---|---|
RenderPageOptions, RenderPageResult | From @gio.js/core/testing: renderPage()'s options and result (status, html, props, setCookies, cacheable, ...). |
CallRouteOptions, RouteResponse | callRoute()'s options and fetch-like response. |
TestRequestOptions, TestRenderError | The options both share, and a render error. |
TestServerOptions, TestServer | createTestServer()'s options and handle (url, port, logs(), close()). |
GioVitestPlugin | From @gio.js/core/vitest: the plugin gioVitest() returns. |
Examples
A typed page with getServerSideProps
import { notFound } from '@gio.js/core';
import type { GetServerSideProps, InferPageProps, GenerateMetadata } from '@gio.js/core';
interface Post {
slug: string;
title: string;
body: string;
}
const posts: Post[] = [{ slug: 'hello', title: 'Hello', body: 'First post.' }];
export const revalidate = 300;
export const getServerSideProps = (async (ctx) => {
const post = posts.find((p) => p.slug === ctx.params.slug); // ctx.params: { slug: string }
if (post === undefined) notFound();
return { props: { post }, tags: [`post:${post.slug}`] };
}) satisfies GetServerSideProps<{ post: Post }, '/blog/:slug'>;
export const generateMetadata: GenerateMetadata<'/blog/:slug'> = (ctx, { props }) => {
const post = props?.['post'] as Post | undefined;
return { title: post?.title ?? ctx.params.slug };
};
export default function BlogPost({ post }: InferPageProps<typeof getServerSideProps>) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}satisfies keeps the exact return type, so InferPageProps reads { post: Post } off it. notFound() returns never, which narrows post.
A typed route handler
import { notFound } from '@gio.js/core';
import type { RouteHandler } from '@gio.js/core';
const titles: Record<string, string> = { '1': 'Hello' };
export const GET: RouteHandler<'/api/posts/:id'> = (req) => {
const title = titles[req.params.id];
if (title === undefined) notFound(); // a JSON 404
return { id: req.params.id, title }; // 200 application/json
};A typed action
import { redirect } from '@gio.js/core';
import type { ActionArgs, WithActionData } from '@gio.js/core';
import { GioForm } from '@gio.js/react';
export async function action(req: ActionArgs) {
const form = await req.formData();
const email = String(form.get('email') ?? '');
if (!email.includes('@')) {
return { status: 422, data: { error: 'Enter a valid email address.' } };
}
return redirect('/subscribe/thanks');
}
export default function Subscribe({ actionData }: WithActionData<typeof action>) {
return (
<GioForm>
<input name="email" type="email" />
{actionData?.error && <p role="alert">{actionData.error}</p>}
<button type="submit">Subscribe</button>
</GioForm>
);
}actionData is { error: string } | undefined: the redirect is left out, because it never re-renders the page.
Typed links and params
import { GioLink, href, useParams } from '@gio.js/react';
export function PostNav() {
const { slug } = useParams<'/blog/:slug'>();
return (
<nav>
<GioLink href={href('/blog/:slug', { slug: 'hello' })}>First post</GioLink>
<span>Reading {slug}</span>
</nav>
);
}A typed sitemap
import type { MetadataRoute } from '@gio.js/core';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: '/', changeFrequency: 'daily', priority: 1 },
{ url: '/blog/hello', lastModified: new Date('2026-10-01') },
];
}Relative URLs resolve against GIO_SITE_URL.
Good to know
- Type errors never stop
gio devorgio start: runtsc --noEmitto see them. - A catch-all param is one string with
/separators ('guides/setup'), in the types and at runtime; onlygetStaticPathsalso accepts an array of segments. process.envvalues are typed asstring | undefinedby@types/node; GioJS adds no declarations for your variables.- In generic code,
ActionArgs<P>['params']is aParamsOf<P>, not aP: TypeScript leaves the conditional type unresolved whilePis a type parameter. gio.config.tsandmiddleware.tsare TypeScript too: wrap them indefineConfig()anddefineMiddleware()for completion. Unknowngio.config.tskeys are an error at startup.
Related
- Typed routes in Linking & Navigating
href,useParamsandgio typegen- Dynamic routes and the .gio directory
getServerSideProps,actionandgenerateMetadata- Environment Variables, Endpoints and Headers
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | Types for every file convention (GetServerSideProps, InferPageProps, PageProps, LayoutProps, ErrorPageProps, GetStaticPaths, RouteHandler, Metadata, MetadataRoute, ...) and for every exported function's parameters and results. .gio/routes.d.ts fills the global GioJS.RegisteredRoutes, read by href(), useParams() and the core types; gio typegen. @gio.js/core ships declaration files (no more TS5097). .gio/css-modules.d.ts types CSS imports. |
v0.1.0-beta.6 | .gio/routes.d.ts and href() introduced, augmenting GioRegisteredRoutes. |