GioJSdocs
On this page

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.

app/posts/[id]/page.tsx
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:

tsconfig.json
{
  "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" in include turns on typed routes and the types of CSS imports. gio doctor warns when it is missing.
  • moduleResolution: "Bundler" matches how esbuild resolves imports, and lets TypeScript read the packages' exports maps (@gio.js/core/testing, @gio.js/core/server-only).
  • paths and jsx settings are honored on the server and when bundling: the worker, the client bundles and standalone bundles all use the project's tsconfig.json (or jsconfig.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:

package.json
{
  "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.

.gio/routes.d.ts
/// <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

FolderPatternParams
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//aboutRecord<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:

ts
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:

TypeDescription
RoutePatternThe registered patterns once .gio/routes.d.ts is included, any string before that.
RouteOrParamsWhat 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:

types/routes.d.ts
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

TypeDescription
PageProps<Route>Props of a page without getServerSideProps: params and searchParams.
LayoutPropschildren, and path: the page's path without query.
ErrorPagePropsProps of error.tsx: { error: { message, digest? }, reset? }. Same as GioErrorProps.
GioErrorProps, GioErrorInfoThe error boundary's props and its error; message is Internal Server Error in production.
NotFoundPagePropsnot-found.tsx renders with no props.

Data fetching

TypeDescription
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().
GsspResponseHeadersRecord<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

TypeDescription
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.
PageActionThe type of a page module's action export.
ActionRedirect, RedirectInitWhat redirect(url, init) returns, and its second argument (status, headers).
GioFormProps, GioFormResult, GioFormStateFrom @gio.js/react: the <GioForm> props, the result its callbacks get, and useGioFormState()'s { pending, lastResult }.

Route handlers and realtime

TypeDescription
RouteHandler<Route>A route.ts method handler: (req: GioRequest<Route>) => unknown.
RouteHandlerFnThe 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, SseCleanupFnThe 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, GioSocketA route.ts wsHandler and the socket it gets (send, close, join, leave, on, params, cookies, ...).
BroadcastOptionsbroadcast()'s options: { except?: socketId }.
UseWebSocketOptions, UseWebSocketResult, ReconnectOptions, WebSocketDataFrom @gio.js/react: useWebSocket()'s options, result, backoff settings and message type.

Metadata

TypeDescription
MetadataA page or layout's metadata: title, description, openGraph, twitter, alternates, robots, icons, themeColor, other and more.
GenerateMetadata<Route>, MetadataContext<Route>, MetadataExtrasgenerateMetadata(ctx, { props }) and its two arguments.
TitleTemplate{ default?, template?, absolute? }.
OpenGraphMetadata, OpenGraphImage, TwitterMetadata, TwitterImageSocial cards.
AlternatesMetadata, RobotsMetadata, RobotsDirectivesCanonical and language URLs, and robots directives.
IconsMetadata, IconDescriptor, ThemeColorDescriptor, MetadataAuthorIcons, theme colors and authors.
MetadataRoute.Sitemap, MetadataRoute.Robots, MetadataRoute.ManifestReturn types of app/sitemap.ts, app/robots.ts and app/manifest.ts.
Sitemap, SitemapEntry, ChangeFrequency, Robots, RobotsRule, ManifestThe same, by their own names.
JsonLdProps, JsonLdDataFrom @gio.js/react: <JsonLd>'s props.

Middleware, config and plugins

TypeDescription
MiddlewareRulesWhat defineMiddleware() takes: redirects, rewrites, headers, guards.
MiddlewareRedirect, MiddlewareRewrite, MiddlewareHeaderRule, MiddlewareGuardOne rule of each kind.
GioConfiggio.config.ts's shape (plugins), what defineConfig() takes.
GioNodePluginA Node plugin: name, version, onRequest, onResponse, onStartup, onShutdown.
IPCRequest, IPCResponseThe request and response a plugin's hooks see.

Sessions, cookies and revalidation

TypeDescription
SessionStorage<Data>, SessionStorageOptionscreateSessionStorage()'s result and options.
Session<Data>, SessionData, SessionSource, CommitSessionOptionsA session, its data, what getSession() reads from (a request, a context, a socket or a cookie header), and commitSession()'s options.
CookieOptionsserializeCookie()'s options, with secure defaults.
RevalidateResult, RevalidatePathOptions{ ok, purged, error? } from revalidateTag / revalidatePath, and { type?: 'page' | 'prefix' }.

Client router and components

TypeDescription
GioRouteruseRouter(): push, replace, back, forward, refresh, prefetch.
NavigateOptions, RouterNavigateOptionsOptions of navigate() (replace, scroll, transition) and of router.push / replace (the same without replace).
ReadonlyURLSearchParamsuseSearchParams(): URLSearchParams without the mutating methods.
TransitionPreset, AnimatePresetNames of the view-transition and <Animate> presets.
GioRegisteredRoutes, RouteParamsOf, RoutePatternThe route registry as @gio.js/react sees it, a pattern's params (registered, or parsed from the pattern), and the patterns href() accepts.

Testing

TypeDescription
RenderPageOptions, RenderPageResultFrom @gio.js/core/testing: renderPage()'s options and result (status, html, props, setCookies, cacheable, ...).
CallRouteOptions, RouteResponsecallRoute()'s options and fetch-like response.
TestRequestOptions, TestRenderErrorThe options both share, and a render error.
TestServerOptions, TestServercreateTestServer()'s options and handle (url, port, logs(), close()).
GioVitestPluginFrom @gio.js/core/vitest: the plugin gioVitest() returns.

Examples

A typed page with getServerSideProps

app/blog/[slug]/page.tsx
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

app/api/posts/[id]/route.ts
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

app/subscribe/page.tsx
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.

components/PostNav.tsx
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

app/sitemap.ts
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 dev or gio start: run tsc --noEmit to see them.
  • A catch-all param is one string with / separators ('guides/setup'), in the types and at runtime; only getStaticPaths also accepts an array of segments.
  • process.env values are typed as string | undefined by @types/node; GioJS adds no declarations for your variables.
  • In generic code, ActionArgs<P>['params'] is a ParamsOf<P>, not a P: TypeScript leaves the conditional type unresolved while P is a type parameter.
  • gio.config.ts and middleware.ts are TypeScript too: wrap them in defineConfig() and defineMiddleware() for completion. Unknown gio.config.ts keys are an error at startup.

Version history

VersionChanges
v0.1.0-beta.8Types 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.