9.0 KiB
9.0 KiB
Next.js App Router File Templates
This document provides basic templates for the common file types used in a Next.js application with the App Router. These templates follow the current best practices for Next.js 15.
Page Templates
Basic Page (page.tsx
)
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Page Title',
description: 'Page description',
}
export default function Page() {
return (
<div>
<h1>Page Content</h1>
{/* Page content goes here */}
</div>
)
}
Dynamic Route Page ([slug]/page.tsx
)
export async function generateMetadata({ params }) {
return {
title: `Page for ${params.slug}`,
description: `Description for ${params.slug}`,
}
}
export default function DynamicPage({ params }) {
const { slug } = params
return (
<div>
<h1>Dynamic Page: {slug}</h1>
{/* Dynamic page content goes here */}
</div>
)
}
Layout Templates
Root Layout (layout.tsx
)
import { Metadata } from 'next'
import './globals.css'
export const metadata: Metadata = {
title: {
template: '%s | Site Name',
default: 'Site Name',
},
description: 'Site description',
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>
{/* Header content */}
</header>
<main>{children}</main>
<footer>
{/* Footer content */}
</footer>
</body>
</html>
)
}
Nested Layout ((dashboard)/layout.tsx
)
export default function DashboardLayout({ children }) {
return (
<div className="dashboard-layout">
<nav className="dashboard-nav">
{/* Dashboard navigation */}
</nav>
<div className="dashboard-content">
{children}
</div>
</div>
)
}
Auth Layout with Session Check ((authenticated)/layout.tsx
)
import { redirect } from 'next/navigation'
// This is a Server Component
export default async function AuthLayout({ children }) {
const session = await getSession() // Replace with your auth solution
if (!session) {
redirect('/login')
}
return <>{children}</>
}
Error Handling Templates
Not Found (not-found.tsx
)
import Link from 'next/link'
export const metadata = {
title: 'Not Found',
description: 'Page not found',
}
export default function NotFound() {
return (
<div className="error-container">
<h1>404 - Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
<Link href="/">
Return to Home
</Link>
</div>
)
}
Error Page (error.tsx
)
'use client' // Error components must be Client Components
import { useEffect } from 'react'
export default function Error({ error, reset }) {
useEffect(() => {
// Log the error to an error reporting service
console.error(error)
}, [error])
return (
<div className="error-container">
<h2>Something went wrong!</h2>
<button
onClick={
// Attempt to recover by trying to re-render the segment
() => reset()
}
>
Try again
</button>
</div>
)
}
Global Error (global-error.tsx
)
'use client'
export default function GlobalError({ error, reset }) {
return (
<html>
<body>
<div className="global-error">
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
</body>
</html>
)
}
Loading States
Loading Component (loading.tsx
)
export default function Loading() {
return (
<div className="loading-container">
<div className="loading-spinner"></div>
<p>Loading...</p>
</div>
)
}
Route Handlers
API Route (api/route.ts
)
import { NextResponse } from 'next/server'
export async function GET(request) {
// Handle GET request
return NextResponse.json({ message: 'Hello World' })
}
export async function POST(request) {
// Handle POST request
const body = await request.json()
return NextResponse.json({ received: body })
}
Dynamic API Route (api/[slug]/route.ts
)
import { NextResponse } from 'next/server'
export async function GET(request, { params }) {
const { slug } = params
return NextResponse.json({ slug })
}
Middleware
Middleware (middleware.ts
)
Place this file in the root of your project:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function runs before requests are completed
export function middleware(request: NextRequest) {
const currentUrl = request.nextUrl.clone()
// Example: check if user is authenticated
const isAuthenticated = checkAuth(request)
if (!isAuthenticated && currentUrl.pathname.startsWith('/protected')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
// Configure matcher for paths that should trigger middleware
export const config = {
matcher: ['/protected/:path*', '/api/:path*'],
}
// Example auth check function
function checkAuth(request) {
// Implement your auth check logic here
return !!request.cookies.get('auth-token')
}
Utilities - Server Actions
Form Actions (actions.ts
)
'use server'
export async function submitForm(formData: FormData) {
// Validate data
const name = formData.get('name')
const email = formData.get('email')
// Process data
try {
// Save to database or send to API
await saveToDatabase({ name, email })
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
}
async function saveToDatabase(data) {
// Implementation for database interaction
}
Server Component with Data Fetching
Data Fetching in Server Component (dashboard/page.tsx
)
// This is a Server Component
export default async function DashboardPage() {
// This data fetching happens on the server
const data = await fetchDashboardData()
return (
<div>
<h1>Dashboard</h1>
<div className="dashboard-stats">
{data.map(item => (
<div key={item.id} className="stat-card">
<h3>{item.title}</h3>
<p>{item.value}</p>
</div>
))}
</div>
</div>
)
}
async function fetchDashboardData() {
// API or database call
const res = await fetch('https://api.example.com/dashboard-data', {
cache: 'no-store' // Don't cache this data
})
if (!res.ok) {
throw new Error('Failed to fetch dashboard data')
}
return res.json()
}
Client Component with Hooks
Interactive Client Component (components/Counter.tsx
)
'use client' // Mark as Client Component
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>
)
}
Parallel Routes
Parallel Route Layout (@dashboard/page.tsx
and @profile/page.tsx
)
In your directory structure:
app/
layout.tsx
[user]/
layout.tsx
page.tsx
@dashboard/
page.tsx
@profile/
page.tsx
In [user]/layout.tsx
:
export default function UserLayout({ children, dashboard, profile }) {
return (
<div className="user-page">
<div className="sidebar">
{/* Sidebar navigation */}
</div>
<div className="main-content">
{children}
</div>
<div className="dashboard-slot">
{dashboard}
</div>
<div className="profile-slot">
{profile}
</div>
</div>
)
}
Route Interception
Photo Modal Example (./(.)photos/[id]/page.tsx
)
The directory structure might look like:
app/
photos/
page.tsx
[id]/
page.tsx
(.)photos/
[id]/
page.tsx // This intercepts /photos/[id]
In ./(.)photos/[id]/page.tsx
:
export default function PhotoModal({ params }) {
const { id } = params
// Fetch photo data based on id
return (
<div className="modal">
<div className="modal-content">
<h2>Photo {id}</h2>
<img src={`/api/photos/${id}`} alt={`Photo ${id}`} />
</div>
</div>
)
}