Overview

In the App Router, folders under app/ are URL segments and reserved file names define what each segment does: page.tsx makes it routable, layout.tsx wraps children, and loading.tsx, error.tsx, and not-found.tsx cover loading, failure, and 404 states. The router is server-first, built on react-server-components. A folder with no page.tsx produces no route.

Put one folder per segment under app/

app/
  layout.tsx        // root layout (required)
  page.tsx          // "/"
  posts/[slug]/
    page.tsx        // "/posts/:slug"

Use [param] for one dynamic segment, [...slug] for a catch-all, and [[...slug]] for an optional catch-all. params and searchParams are Promises; see nextjs-async-dynamic-apis. A route.ts cannot sit beside a page.tsx in the same segment.

Use layouts for shells and pages for content

A layout.tsx wraps every page below it, persists across navigations between its children, and does not re-render on them. Layouts do not receive searchParams. Put nav, providers, theme, and auth context there. The root app/layout.tsx is the only required file and must render <html> and <body>.

Type route props with the generated helpers

Run next typegen (also run by next dev and next build) to get global PageProps<'/blog/[slug]'>, LayoutProps, and RouteContext types, so you do not hand-write Promise<{ ... }> for every page.

export default async function Page(props: PageProps<"/blog/[slug]">) {
  const { slug } = await props.params;
  return <h1>{slug}</h1>;
}

In 16.3, import { lang } from "next/root-params" reads root-level params such as [lang] from any Server Component without prop drilling.

Group routes without changing the URL

Wrap a folder name in parentheses: app/(marketing)/about/page.tsx serves /about. Groups exist to give sections their own layouts, for example a marketing shell and an authenticated shell, not to shape URLs.

Render independent panes with parallel routes

A folder named @feed or @sidebar is passed to the parent layout as a prop with the same name, and each slot has its own loading and error state. As of Next.js 16, every slot needs an explicit default.js; builds fail without it. Return null or call notFound() in default.js to keep the old behavior.

// app/layout.tsx
export default function Layout(
  { children, sidebar }: { children: React.ReactNode; sidebar: React.ReactNode },
) {
  return (<><main>{children}</main><aside>{sidebar}</aside></>);
}

Intercept routes for modals over a base page

An intercepting route renders another route inside the current layout using (.), (..), or (...) prefixes that mirror relative paths. Combine it with a parallel @modal slot so a thumbnail click opens a modal while a direct visit or refresh renders the full page.

app/photos/[id]/page.tsx          // full page
app/@modal/(.)photos/[id]/page.tsx // modal on client navigation
app/@modal/default.tsx             // required: return null

Migrate from the Pages Router one route at a time

app/ and pages/ coexist, and a route cannot exist in both.

  • getServerSideProps becomes an async server component that awaits data inline.
  • getStaticProps and getStaticPaths become a server component with generateStaticParams.
  • _app.tsx becomes app/layout.tsx plus per-segment layouts.
  • pages/api/* becomes [[frontend/nextjs-route-handlers|route.ts handlers]], and form mutations become server actions.