Skip to content
C.W.K.
Stream
Lesson 04 of 07 · published

Next.js 16: App Router + TS

~9 min · frameworks, nextjs, next-16, app-router

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"Next.js 16 fully embraces React 19 Server Components. TypeScript follows naturally."

The App Router structure

Next 16's App Router (the only router for new projects) uses file-based routing under app/. app/page.tsx is the root page. app/posts/[id]/page.tsx is a dynamic route. app/layout.tsx wraps children. Each file exports either a default component (page/layout) or specific named exports (loading, error, not-found).

Route segment props are typed automatically

Page and layout components receive typed props from the router. export default async function Page({ params, searchParams }: { params: Promise<{ id: string }>; searchParams: Promise<{ q?: string }> }). Note that in Next 16, both params and searchParams are async — you await them before reading. This is part of React's broader async-first model.

Data fetching and revalidation

In Server Components, you fetch directly: const data = await fetch('https://api.example.com/posts').then(r => r.json()). Since Next 15, fetch requests are not cached by default. Opt a request into the Data Cache with { cache: 'force-cache' }, set a lifetime with { next: { revalidate: 60 } }, or use Next 16 Cache Components and the 'use cache' directive for explicit cached scopes. Treat caching as an intentional runtime policy, not an automatic side effect of calling fetch.

Next.js 16 is React 19 with conventions and infrastructure. Once you understand React 19's Server Components + Actions model, Next.js mostly stays out of your way.

Code

Next.js 16 App Router — page, layout, loading·tsx
// app/posts/[id]/page.tsx
export default async function PostPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;     // Next 16 — params is async
  const post = await fetch(`https://api.example.com/posts/${id}`).then((r) => r.json());

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.body }} />
    </article>
  );
}

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

// app/posts/loading.tsx — Suspense fallback for /posts and its children.
export default function Loading() {
  return <div>Loading posts…</div>;
}

External links

Exercise

Set up a small Next.js 16 app with a /posts listing page and a /posts/[id] detail page. Both should fetch from a mock API (JSONPlaceholder or your own). Confirm the params are typed and the await works in async server components.
Hint
Use create-next-app to scaffold. The page components default to Server Components. The params object is Promise<{ id: string }> — await it before accessing .id.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.