Filesystem router

Routes are data at build time.

Turtle validates the application tree, rejects ambiguity early, and embeds a deterministic route manifest into the server graph.

Conventions

FileURLMeaning
app/page.tsx/Static page
app/docs/[slug]/page.tsx/docs/:slugDynamic string parameter
app/files/[...parts]/page.tsx/files/*partsRequired catch-all string array
app/(site)/about/page.tsx/aboutRoute group is invisible
app/api/items/route.ts/api/itemsRoute handler

Page inputs

type PageProps = {
  params: Promise<Record<string, string | string[]>>;
  searchParams: Promise<Record<string, string | string[]>>;
};

export default async function Page({ params, searchParams }: PageProps) {
  const { slug } = await params;
  const query = await searchParams;
  return <h1>{slug}: {query.mode}</h1>;
}

Repeated query names become arrays. Malformed percent-encoding does not reach application code; it produces a not-found response.

Layouts and boundaries

Every layout.tsx from the root through the matched directory wraps the page. Route groups can contribute layouts without changing the URL. The nearest error.tsx handles ordinary render failures, while the nearest not-found.tsx handles notFound(). An unmatched URL uses the root not-found UI and root layout.

Static parameter discovery

export function generateStaticParams() {
  return [{ slug: "install" }, { slug: "routing" }];
}

Turtle calls this during the build, validates that it returns records containing strings or string arrays, and stores the result in the manifest.

Build-time diagnostics

Turtle rejects colliding route groups, semantically equivalent dynamic names such as [id] and [slug], malformed brackets, duplicate parameter names, non-terminal catch-alls, and unsupported conventions such as loading.tsx and middleware.ts. Catch-all segments require at least one value.