442 lines
9.0 KiB
Markdown
442 lines
9.0 KiB
Markdown
# 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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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:
|
|
|
|
```ts
|
|
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`)
|
|
|
|
```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`)
|
|
|
|
```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`)
|
|
|
|
```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`:
|
|
```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`:
|
|
```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>
|
|
)
|
|
}
|
|
```
|
|
|
|
## References
|
|
|
|
- [Official Next.js Documentation](https://nextjs.org/docs/app)
|
|
- [Pages and Layouts](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts)
|
|
- [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers)
|
|
- [Error Handling](https://nextjs.org/docs/app/building-your-application/routing/error-handling)
|
|
- [Loading UI and Streaming](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming)
|
|
- [Metadata API](https://nextjs.org/docs/app/building-your-application/optimizing/metadata)
|
|
- [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions)
|
|
- [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware)
|