Skip to main content

What is Next.js?

  • For Beginners
  • Why We Use It
Next.js is a React framework that helps you build full-stack web applications with ease. Think of it as React with superpowers:
  • React lets you build interactive user interfaces using components
  • Next.js adds powerful features like server-side rendering, routing, and API endpoints out of the box
Instead of configuring everything yourself (bundlers, routers, servers), Next.js provides a complete solution so you can focus on building your app. It’s like having a pre-built house foundation instead of starting from scratch.Key benefits for learners:
  • File-based routing (create a file, get a page automatically)
  • Built-in optimization for performance and SEO
  • Can handle both frontend and backend code in one project
  • Excellent documentation and large community support

SSR/ISR

Server-side rendering and incremental static regeneration

API Routes

Built-in API layer with middleware support

App Router

Modern routing with layouts and loading states

When to Use

  • Always Use For
  • Consider Alternatives
  • Full-stack applications
  • SEO-critical sites
  • E-commerce platforms
  • Dashboards with auth
  • Real-time features

Project Setup

1

Create New Project

pnpm create next-app@latest my-app --typescript --tailwind --app --src-dir
2

Add Essential Packages

pnpm add @supabase/supabase-js @tanstack/react-query zod
pnpm add -D @types/node
3

Configure TypeScript

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Best Practices

File Structure

src/
├── app/
│   ├── (auth)/
│   │   ├── login/
│   │   └── register/
│   ├── (dashboard)/
│   │   └── layout.tsx
│   ├── api/
│   │   └── route.ts
│   └── layout.tsx
├── components/
│   ├── ui/
│   └── features/
├── lib/
│   ├── supabase.ts
│   └── utils.ts
└── types/

Data Fetching

// app/products/page.tsx
async function ProductsPage() {
  const products = await fetch('https://api.example.com/products', {
    next: { revalidate: 3600 } // ISR: revalidate every hour
  }).then(res => res.json())

  return <ProductList products={products} />
}

API Routes

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const userSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
})

export async function POST(request: NextRequest) {
  try {
    const body = await request.json()
    const validated = userSchema.parse(body)

    // Create user in database
    const user = await createUser(validated)

    return NextResponse.json(user, { status: 201 })
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: error.errors },
        { status: 400 }
      )
    }
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

Performance Optimization

import Image from 'next/image'

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1920}
  height={1080}
  priority // Load immediately for above-the-fold
  placeholder="blur"
  blurDataURL={blurDataUrl}
/>
import dynamic from 'next/dynamic'

const HeavyComponent = dynamic(
  () => import('@/components/HeavyComponent'),
  {
    loading: () => <Skeleton />,
    ssr: false // Disable SSR for client-only components
  }
)
// app/products/[id]/page.tsx
export async function generateMetadata({ params }) {
  const product = await getProduct(params.id)

  return {
    title: product.name,
    description: product.description,
    openGraph: {
      images: [product.image]
    }
  }
}

Common Patterns

Authentication with Supabase

// middleware.ts
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'

export async function middleware(req) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })

  const { data: { session } } = await supabase.auth.getSession()

  if (!session && req.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', req.url))
  }

  return res
}

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

Error Handling

// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  )
}

Deployment

Always use environment variables for sensitive data. Never commit .env.local files.