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
| File | URL | Meaning |
|---|---|---|
app/page.tsx | / | Static page |
app/docs/[slug]/page.tsx | /docs/:slug | Dynamic string parameter |
app/files/[...parts]/page.tsx | /files/*parts | Required catch-all string array |
app/(site)/about/page.tsx | /about | Route group is invisible |
app/api/items/route.ts | /api/items | Route 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.