1// app/layout.tsx — Root layout (Server Component by default)
2export default function RootLayout({ children }: { children: React.ReactNode }) {
3 return <html lang="en"><body>{children}</body></html>;
4}
5
6// app/posts/[id]/page.tsx — Dynamic Server Component with fetch
7import { notFound } from 'next/navigation';
8
9type Props = { params: { id: string } };
10
11export async function generateStaticParams() {
12 const posts = await fetch('https://api.example.com/posts').then(r => r.json());
13 return posts.map((p: { id: string }) => ({ id: p.id }));
14}
15
16export default async function PostPage({ params }: Props) {
17 const post = await fetch(`https://api.example.com/posts/${params.id}`, {
18 next: { revalidate: 3600 }, // ISR: revalidate every hour
19 }).then(r => r.json());
20
21 if (!post) notFound();
22 return <article><h1>{post.title}</h1><p>{post.body}</p></article>;
23}
24
25// app/posts/[id]/comments/page.tsx — Loading and Error boundaries
26// loading.tsx — shown while page.tsx is fetching
27export default function Loading() {
28 return <div className="skeleton-loader">Loading comments...</div>;
29}
30
31// error.tsx — catches errors in the route segment
32'use client'; // error.tsx must be a Client Component
33import { useEffect } from 'react';
34
35export default function Error({ error, reset }: { error: Error; reset: () => void }) {
36 useEffect(() => console.error(error), [error]);
37 return <div>Error: {error.message} <button onClick={reset}>Retry</button></div>;
38}
39
40// app/api/posts/route.ts — Route Handler (API endpoint)
41import { NextRequest, NextResponse } from 'next/server';
42
43export async function GET(req: NextRequest) {
44 const posts = await getPosts();
45 return NextResponse.json(posts);
46}
47
48export async function POST(req: NextRequest) {
49 const body = await req.json();
50 const created = await createPost(body);
51 return NextResponse.json(created, { status: 201 });
52}
53
54async function getPosts() { return []; }
55async function createPost(body: any) { return body; }