GioJSdocs
On this page

page.tsx

The file that makes a folder under app/ a route. Its default export is the React component GioJS renders for that URL.

app/blog/[slug]/page.tsx
import React from 'react';
import type { PageProps } from '@gio.js/core';

export default function BlogPost({ params, searchParams }: PageProps<'/blog/:slug'>) {
  return (
    <article>
      <h1>{params.slug}</h1>
      {searchParams.preview === '1' && <p>Preview mode</p>}
    </article>
  );
}

Reference

File name and location

A page file may sit in app/ or any folder below it, except inside a private folder. The folder path is the URL: app/page.tsx answers /, app/blog/[slug]/page.tsx answers /blog/:slug, and route groups add nothing to it.

ExtensionPrecedence
page.tsxUsed first
page.jsxUsed when there is no page.tsx
page.jsUsed when there is neither

A folder with several of them uses the first one and ignores the others, so a stray compiled page.js next to page.tsx never changes what renders. page.ts is not a page: pages contain JSX.

Props

A page without getServerSideProps renders with these props:

PropTypeDefaultDescription
paramsRecord<string, string>-The dynamic segments of the URL, by folder name: { slug: 'hello' }. A catch-all is one string with its / separators ('a/b'), an empty optional catch-all is '', and a static route gets {}.
searchParamsRecord<string, string>-The query string, decoded (+ becomes a space). One value per name: for ?tag=a&tag=b the last one wins.
actionDataunknown-Only on the render that answers a POST to a page with an action: what the action returned.

A page with getServerSideProps renders with exactly the props it returned, plus actionData after an action. Type them with InferPageProps<typeof getServerSideProps>; type the plain props with PageProps<'/blog/:slug'>. Both come from @gio.js/core, and the route pattern is checked against your routes (see .gio/).

Module exports

The default export is required. Everything else is optional:

ExportWhat it does
defaultThe page component.
getServerSidePropsLoads the props on the server for every render; can redirect or answer 404.
actionHandles POST requests to the page's URL (forms).
metadata / generateMetadataTitle, description and the other <head> tags.
revalidateCaches the rendered page for that many seconds.
tagsCache tags for revalidateTag().
shell'cache' turns on partial prerendering.
getStaticPathsThe params gio export writes for a dynamic route.

All page exports are listed on Page Exports. getServerSideProps and getStaticPaths are removed from the browser bundle, together with the imports only they use.

Behavior

  • Methods. A page answers GET and HEAD, and POST when it exports action. Any other method gets 405 with a JSON body {"error":"Method Not Allowed"} and an Allow header (GET, HEAD, plus POST with an action, plus the methods of a route.ts in the same folder).
  • Rendering. The page renders on the server inside its layouts, then hydrates in the browser from a per-route bundle. Its props travel in the HTML as JSON (<script id="__gio_props">), so they must be JSON-serializable: a page whose props are not renders without hydration, with a warning in the log.
  • Status. 200 with Content-Type: text/html; charset=utf-8. notFound() answers 404 with the nearest not-found.tsx, and a thrown error 500 with the nearest error.tsx.
  • Caching. Without revalidate every request renders and the response is Cache-Control: private, no-cache. See Caching & Revalidating.
  • Navigation. On a soft navigation the page remounts whenever the path changes (/posts/1 to /posts/2), so its state starts fresh; a query-only change keeps it mounted.

Examples

Read params and the query string

app/shop/[category]/page.tsx
import React from 'react';
import type { PageProps } from '@gio.js/core';

export default function Category({ params, searchParams }: PageProps<'/shop/:category'>) {
  const sort = searchParams.sort ?? 'popular';
  return (
    <h1>
      {params.category} - sorted by {sort}
    </h1>
  );
}

/shop/shoes?sort=price renders "shoes - sorted by price".

Load data on the server

app/posts/[id]/page.tsx
import React from 'react';
import { notFound } from '@gio.js/core';
import type { GetServerSideProps, InferPageProps } from '@gio.js/core';
import { getPost } from '../../../lib/posts';

export const getServerSideProps: GetServerSideProps<{ title: string; body: string }, '/posts/:id'> =
  async (ctx) => {
    const post = await getPost(ctx.params.id);
    if (post === null) notFound();
    return { props: { title: post.title, body: post.body } };
  };

export default function Post({ title, body }: InferPageProps<typeof getServerSideProps>) {
  return (
    <article>
      <h1>{title}</h1>
      <p>{body}</p>
    </article>
  );
}

A JavaScript page

app/about/page.jsx
/** @param {import('@gio.js/core').PageProps<'/about'>} props */
export default function About({ searchParams }) {
  return <h1>About{searchParams.ref ? ` (from ${searchParams.ref})` : ''}</h1>;
}

Good to know

  • Only the page file (and the other special names on File Conventions) is routed. Components, styles and tests can live next to it in the same folder without becoming URLs.
  • A page is not a React Server Component. It renders on the server and again in the browser, so it cannot be an async function and cannot read files or secrets itself: load data in getServerSideProps.
  • Unlike Next.js, params and searchParams are plain objects, not promises, and a page with getServerSideProps does not receive them unless it returns them.
  • Param values are taken from the path as sent. GioJS decodes escapes of unreserved characters (%41 is A), but other escapes stay encoded: /blog/hello%20world gives { slug: 'hello%20world' }. Call decodeURIComponent() when you need the text.
  • Two pages that answer the same URLs (for example in two route groups) stop startup with an error naming both files. A route.ts in the same folder is allowed: it answers the methods it exports, and the page the rest.
  • Everything in the props ends up in the page's HTML. Never return secrets from getServerSideProps.

Version history

VersionChanges
v0.1.0-beta.8Pages answer POST through an action export. Pages inside (group) folders no longer carry the group in their URL, pages in _private folders are never routed, and two pages answering the same URLs stop startup. PageProps and InferPageProps types.
v0.1.0-beta.5Pages hydrate in the browser from per-route client bundles.
v0.1.0-beta.1Introduced, as page.tsx, page.jsx or page.js.