Ready-to-use templates for pages, API routes, layouts, and configurations
Next.js is a React framework that provides routing, rendering, data fetching, and more. Version 13+ introduced the App Router with React Server Components, streaming, and improved layouts. This collection covers both App Router (recommended) and Pages Router patterns.
These templates follow Next.js best practices and include TypeScript examples, server components, client components, API routes, middleware, and configuration files.
New routing system with server components and streaming
React components that render on the server by default
Build backend APIs directly in your Next.js app
Shared UI that persists across pages
In the App Router, pages are Server Components by default. Create a page.tsx file in any folder under app/.
import Link from 'next/link'; export default function HomePage() { return ( <main className="container"> <h1>Welcome to Next.js</h1> <p>This is a server component - it runs on the server.</p> <Link href="/about"> About Page </Link> </main> ); } // Metadata for SEO export const metadata = { title: 'Home Page', description: 'Welcome to our Next.js application', };
interface PageProps { params: { slug: string; }; } export default async function BlogPost({ params }: PageProps) { // Fetch data directly in server component const post = await getPost(params.slug); return ( <article> <h1>{post.title}</h1> <p>{post.content}</p> </article> ); } // Generate static params at build time export async function generateStaticParams() { const posts = await getAllPosts(); return posts.map((post) => ({ slug: post.slug, })); } // Generate metadata dynamically export async function generateMetadata({ params }: PageProps) { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt, }; }
Use 'use client' directive when you need interactivity, state, or browser APIs.
'use client'; import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}> Increment </button> </div> ); }
import { Inter } from 'next/font/google'; import './globals.css'; const inter = Inter({ subsets: ['latin'] }); export const metadata = { title: { default: 'My App', template: '%s | My App', }, description: 'My awesome Next.js application', }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body className={inter.className}> <header> <nav>{/* Navigation */}</nav> </header> {children} <footer>{/* Footer */}</footer> </body> </html> ); }
export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { return ( <div className="dashboard"> <aside> {/* Sidebar navigation */} </aside> <main>{children}</main> </div> ); }
import { NextResponse } from 'next/server'; // GET /api/users export async function GET(request: Request) { const { searchParams } = new URL(request.url); const page = searchParams.get('page') || '1'; const users = await fetchUsers(parseInt(page)); return NextResponse.json({ users }); } // POST /api/users export async function POST(request: Request) { const body = await request.json(); // Validate input if (!body.name || !body.email) { return NextResponse.json( { error: 'Name and email required' }, { status: 400 } ); } const user = await createUser(body); return NextResponse.json(user, { status: 201 }); }
import { NextResponse } from 'next/server'; export async function GET( request: Request, { params }: { params: { id: string } } ) { const user = await getUserById(params.id); if (!user) { return NextResponse.json( { error: 'User not found' }, { status: 404 } ); } return NextResponse.json(user); } export async function DELETE( request: Request, { params }: { params: { id: string } } ) { await deleteUser(params.id); return new Response(null, { status: 204 }); }
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Authentication check const token = request.cookies.get('token'); if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } // Add custom header const response = NextResponse.next(); response.headers.set('x-custom-header', 'custom-value'); return response; } // Configure which paths middleware runs on export const config = { matcher: ['/dashboard/:path*', '/api/:path*'], };
/** @type {import('next').NextConfig} */ const nextConfig = { // Image optimization images: { domains: ['example.com', 'cdn.example.com'], formats: ['image/avif', 'image/webp'], }, // Environment variables env: { CUSTOM_KEY: process.env.CUSTOM_KEY, }, // Redirects async redirects() { return [ { source: '/old-page', destination: '/new-page', permanent: true, }, ]; }, // Headers async headers() { return [ { source: '/api/:path*', headers: [ { key: 'Access-Control-Allow-Origin', value: '*' }, ], }, ]; }, // Experimental features experimental: { serverActions: true, }, }; module.exports = nextConfig;
// Static data (cached by default) async function getStaticData() { const res = await fetch('https://api.example.com/data'); return res.json(); } // Dynamic data (no caching) async function getDynamicData() { const res = await fetch('https://api.example.com/data', { cache: 'no-store' }); return res.json(); } // Revalidate data every 60 seconds async function getRevalidatedData() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 60 } }); return res.json(); } export default async function Page() { const data = await getStaticData(); return <div>{/* Render data */}</div>; }
export default function Loading() { return ( <div className="loading"> <div className="spinner"></div> <p>Loading...</p> </div> ); }
'use client'; // Error components must be Client Components export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { return ( <div> <h2>Something went wrong!</h2> <p>{error.message}</p> <button onClick={() => reset()}> Try again </button> </div> ); }
import Link from 'next/link'; export default function NotFound() { return ( <div> <h2>404 - Page Not Found</h2> <p>Could not find requested resource</p> <Link href="/">Return Home</Link> </div> ); }
Don't: Use 'use client' at the root layout unless absolutely necessary. Keep as much of your app as Server Components as possible for better performance. Avoid fetching data in Client Components when you can do it in Server Components instead.