laconic-deployer-frontend/standards/blueprints/nextjs-templates.md

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>
  )
}

References