Modern Web Development with Next.js 14: The Complete Guide


Modern Web Development with Next.js 14: The Complete Guide

Next.js 14 introduces groundbreaking features that revolutionize React development. This comprehensive guide will help you master the latest version and build modern web applications.

Getting Started

First, create a new Next.js 14 project:

npx create-next-app@latest my-nextjs-app
cd my-nextjs-app
npm run dev

Key Features in Next.js 14

1. Server Components

Server Components are the default in Next.js 14, offering improved performance and reduced client-side JavaScript:

// app/page.tsx
async function getData() {
  const res = await fetch('https://api.example.com/data')
  return res.json()
}

export default async function Page() {
  const data = await getData()
  
  return (
    <main>
      <h1>Server Component Example</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </main>
  )
}

2. App Router

The new App Router provides more intuitive routing and layouts:

// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
        </nav>
        {children}
      </body>
    </html>
  )
}

// app/about/page.tsx
export default function AboutPage() {
  return (
    <div>
      <h1>About Us</h1>
      <p>Welcome to our website!</p>
    </div>
  )
}

3. Server Actions

Server Actions enable form handling without API routes:

// app/form/page.tsx
export default function FormPage() {
  async function handleSubmit(formData: FormData) {
    'use server'
    
    const name = formData.get('name')
    const email = formData.get('email')
    
    // Process form data on the server
    await saveToDatabase({ name, email })
  }
  
  return (
    <form action={handleSubmit}>
      <input name="name" placeholder="Name" />
      <input name="email" type="email" placeholder="Email" />
      <button type="submit">Submit</button>
    </form>
  )
}

Project: Full-Stack Blog Platform

Let's build a complete blog platform with Next.js 14:

// app/types/index.ts
interface Post {
  id: string
  title: string
  content: string
  author: string
  createdAt: Date
}

// app/lib/db.ts
import { Prisma } from '@prisma/client'

const prisma = new PrismaClient()

export async function getPosts() {
  return prisma.post.findMany({
    orderBy: { createdAt: 'desc' }
  })
}

export async function createPost(data: Omit<Post, 'id' | 'createdAt'>) {
  return prisma.post.create({ data })
}

// app/posts/page.tsx
import { getPosts } from '@/lib/db'

export default async function PostsPage() {
  const posts = await getPosts()
  
  return (
    <div className="max-w-4xl mx-auto py-8">
      <h1 className="text-3xl font-bold mb-8">Blog Posts</h1>
      <div className="space-y-8">
        {posts.map((post) => (
          <article key={post.id} className="border rounded-lg p-6">
            <h2 className="text-2xl font-semibold mb-4">{post.title}</h2>
            <p className="text-gray-600 mb-4">{post.content}</p>
            <div className="text-sm text-gray-500">
              By {post.author} • {post.createdAt.toLocaleDateString()}
            </div>
          </article>
        ))}
      </div>
    </div>
  )
}

// app/posts/new/page.tsx
export default function NewPostPage() {
  async function createPost(formData: FormData) {
    'use server'
    
    const title = formData.get('title') as string
    const content = formData.get('content') as string
    const author = formData.get('author') as string
    
    await createPost({ title, content, author })
    redirect('/posts')
  }
  
  return (
    <div className="max-w-2xl mx-auto py-8">
      <h1 className="text-3xl font-bold mb-8">Create New Post</h1>
      <form action={createPost} className="space-y-6">
        <div>
          <label htmlFor="title" className="block text-sm font-medium">
            Title
          </label>
          <input
            type="text"
            name="title"
            id="title"
            className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
            required
          />
        </div>
        
        <div>
          <label htmlFor="content" className="block text-sm font-medium">
            Content
          </label>
          <textarea
            name="content"
            id="content"
            rows={5}
            className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
            required
          />
        </div>
        
        <div>
          <label htmlFor="author" className="block text-sm font-medium">
            Author
          </label>
          <input
            type="text"
            name="author"
            id="author"
            className="mt-1 block w-full rounded-md border-gray-300 shadow-sm"
            required
          />
        </div>
        
        <button
          type="submit"
          className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600"
        >
          Create Post
        </button>
      </form>
    </div>
  )
}

Performance Optimization

1. Image Optimization

Next.js provides automatic image optimization:

import Image from 'next/image'

export default function OptimizedImage() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority
      className="rounded-lg"
    />
  )
}

2. Static Site Generation (SSG)

Generate static pages at build time:

// app/posts/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts()
  
  return posts.map((post) => ({
    slug: post.slug,
  }))
}

export default async function PostPage({
  params,
}: {
  params: { slug: string }
}) {
  const post = await getPostBySlug(params.slug)
  
  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  )
}

3. Incremental Static Regeneration (ISR)

Update static pages without rebuilding:

// app/products/[id]/page.tsx
export default async function ProductPage({
  params,
}: {
  params: { id: string }
}) {
  const product = await fetch(
    `https://api.example.com/products/${params.id}`,
    { next: { revalidate: 3600 } } // Revalidate every hour
  ).then(res => res.json())
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>Price: ${product.price}</p>
    </div>
  )
}

Advanced Features

1. API Routes

Create API endpoints within Next.js:

// app/api/posts/route.ts
import { NextResponse } from 'next/server'

export async function GET() {
  const posts = await getPosts()
  return NextResponse.json(posts)
}

export async function POST(request: Request) {
  const data = await request.json()
  const post = await createPost(data)
  return NextResponse.json(post, { status: 201 })
}

2. Middleware

Add custom middleware for authentication and more:

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token')
  
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  
  return NextResponse.next()
}

export const config = {
  matcher: '/dashboard/:path*',
}

3. Error Handling

Implement error boundaries and loading states:

// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error
  reset: () => void
}) {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
      <p className="text-red-500 mb-4">{error.message}</p>
      <button
        onClick={reset}
        className="bg-blue-500 text-white px-4 py-2 rounded-md"
      >
        Try again
      </button>
    </div>
  )
}

// app/loading.tsx
export default function Loading() {
  return (
    <div className="flex items-center justify-center min-h-screen">
      <div className="animate-spin rounded-full h-32 w-32 border-t-2 border-b-2 border-blue-500" />
    </div>
  )
}

Best Practices

  1. Project Structure

    • Use feature-based organization
    • Keep components small and focused
    • Implement proper TypeScript types
    • Follow consistent naming conventions
  2. Performance

    • Optimize images and fonts
    • Implement proper caching
    • Use code splitting
    • Monitor Core Web Vitals
  3. Security

    • Validate user input
    • Implement proper authentication
    • Use HTTPS
    • Follow security headers
  4. Development Workflow

    • Use ESLint and Prettier
    • Write unit tests
    • Implement CI/CD
    • Document code properly

Conclusion

Next.js 14 provides powerful features for modern web development:

  • Server Components for better performance
  • App Router for intuitive routing
  • Server Actions for simplified form handling
  • Built-in optimizations for production

Keep exploring the ecosystem and stay updated with the latest features and best practices.


Further Reading