Next.js Templates

Ready-to-use templates for pages, API routes, layouts, and configurations

About Next.js 14+

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.

App Router

New routing system with server components and streaming

Server Components

React components that render on the server by default

API Routes

Build backend APIs directly in your Next.js app

Layouts

Shared UI that persists across pages

App Router Page

Server Component (default)

In the App Router, pages are Server Components by default. Create a page.tsx file in any folder under app/.

app/page.tsx App Router
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',
};
app/blog/[slug]/page.tsx Dynamic Route
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,
  };
}

Client Component

For interactivity and hooks

Use 'use client' directive when you need interactivity, state, or browser APIs.

components/Counter.tsx Client Component
'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>
  );
}

When to Use Client Components

  • Need React hooks (useState, useEffect, etc.)
  • Event listeners (onClick, onChange, etc.)
  • Browser-only APIs (localStorage, window, etc.)
  • State management or interactivity

Layouts

Shared UI across pages
app/layout.tsx Root Layout
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>
  );
}
app/dashboard/layout.tsx Nested Layout
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard">
      <aside>
        {/* Sidebar navigation */}
      </aside>
      
      <main>{children}</main>
    </div>
  );
}

API Routes

Backend endpoints in your Next.js app
app/api/users/route.ts App Router API
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 });
}
app/api/users/[id]/route.ts Dynamic API Route
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 });
}

Middleware

Run code before requests complete
middleware.ts
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*'],
};

Configuration

next.config.js options
next.config.js
/** @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;

Data Fetching

Server components & caching
Server Component Data Fetching
// 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>;
}

Loading & Error States

Special files for UX
app/loading.tsx
export default function Loading() {
  return (
    <div className="loading">
      <div className="spinner"></div>
      <p>Loading...</p>
    </div>
  );
}
app/error.tsx
'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>
  );
}
app/not-found.tsx
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>
  );
}

Best Practices

Tips for Next.js development

Next.js Recommendations

  • Use Server Components by default - Only add 'use client' when needed
  • Fetch data where it's used - Colocate data fetching with components
  • Use TypeScript - Better type safety and developer experience
  • Optimize images - Use next/image for automatic optimization
  • Leverage caching - Understand fetch caching and revalidation
  • Use route groups - Organize routes with (folder) syntax
  • Implement error boundaries - Add error.tsx files for better UX
  • Use environment variables - Keep secrets in .env.local

Common Pitfalls

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.