Skip to main content
Tutorials

Adding a page to this site

A short walkthrough of the four files a new page touches here, and the two conventions that keep it from drifting from the rest of the site.

1 min read

ST

Written by

Seratlas Team

Engineering and delivery

1 min read

We build and operate assessment software. We write here about the parts that are harder than they look.

This is the internal version of the instructions, published because it also explains how the site is organised.

One: the copy

Create the strings first, in src/data/, as a single exported object with a section per block of the page. Nothing user-visible is typed into a component or a route here — every text slot is a prop fed from a data file, which is what makes a copy pass possible without touching JSX, and what makes "is this string hard-coded?" a search rather than a reading exercise.

Two: the route

A directory under src/app/ with a page.tsx that exports metadata and a component composing sections from src/components/sections/. Set alternates.canonical to the route's own path; leaving it off lets a stray query string become a second URL for the same page.

If the route has a [slug], two lines are mandatory rather than advisory:

export const dynamicParams = false;

and a notFound() when the lookup misses. Under a static export there is no server to render a param that generateStaticParams did not list, so without the first line the build treats an unlisted slug as something to render on demand, which is a runtime that does not exist.

Three: the navigation

Add it to src/lib/nav.ts and, if it belongs there, to the footer columns. Then add it to src/app/sitemap.ts — that file derives its entries from the same sources the routes generate from, so a new static page is the one case that needs a line by hand. A sitemap listing a URL the export never wrote is a 404 handed straight to a crawler.

Four: the check

npm run verify

Typecheck, lint, tests, build. Then look in out/ for the directory you expected and open its index.html: rendered section text means the page prerendered, and an empty shell means something in it only exists after hydration.

The two conventions

No user-visible string literals under src/app/ or src/components/. And anything that reads from disk lives in a *.server.ts module with import "server-only" at the top, so importing it from a client component is a build error instead of an empty module and a page with no cards on it.

Tags

  • tutorials
  • nextjs

Questions about any of this?

We would rather answer a direct question than write a follow-up post nobody asked for.