nextjs 100 Q&As

Next.js FAQ & Answers

100 expert Next.js answers researched from official documentation. Every answer cites authoritative sources you can verify.

caching_revalidation

10 questions
A

Yes, revalidatePath() invalidates child routes only when using type: 'layout'.

The Behavior Depends on the Type Parameter

With type: 'page' (default):

  • Only invalidates the specific page
  • Does NOT invalidate child routes
  • Example: revalidatePath('/blog/[slug]', 'page') invalidates /blog/[slug] but NOT /blog/[slug]/[author]

With type: 'layout':

  • Invalidates the layout and all pages beneath it
  • DOES invalidate child routes that share the same layout
  • Example: revalidatePath('/blog/[slug]', 'layout') invalidates both /blog/[slug] AND /blog/[slug]/[another]
// Does NOT invalidate child routes
revalidatePath('/blog/[slug]', 'page')

// DOES invalidate child routes
revalidatePath('/blog/[slug]', 'layout')

Sources:

99% confidence
A

revalidatePath() purges cached data for a specific URL path or route pattern, while revalidateTag() purges cached data across all pages that use a specific cache tag.

Key Differences

Scope:

  • revalidatePath('/products/123') - Invalidates one specific path
  • revalidateTag('products') - Invalidates all data tagged with 'products' across your entire app

Granularity:

  • revalidatePath is route-focused: targets pages/layouts by URL
  • revalidateTag is data-focused: targets cached data wherever it appears

Use Case:

  • Use revalidatePath when you update something that affects a specific page (e.g., editing a blog post at /blog/my-post)
  • Use revalidateTag when you update data that appears on multiple pages (e.g., updating product inventory that shows on category pages, search results, and product detail pages)

Example:

// Invalidate a specific product page
revalidatePath('/products/123')

// Invalidate all pages that fetch data tagged with 'products'
revalidateTag('products')

To use tags, you must first tag your data fetches:

fetch('https://api.example.com/products', {
  next: { tags: ['products'] }
})

Recommendation: For most cases, use revalidatePath for simplicity. Use revalidateTag when you need fine-grained control over which data gets revalidated across multiple routes.

Sources:

99% confidence
A

Data Cache stores the results of individual data fetches (like fetch() requests) across server requests and deployments. It caches the raw data returned from external sources.

Full Route Cache stores the fully rendered output of static routes—the React Server Component Payload and HTML—generated at build time or during background revalidation.

Key Differences:

What they cache:

  • Data Cache: Individual fetch request results (data layer)
  • Full Route Cache: Complete rendered route output (presentation layer)

When they operate:

  • Data Cache: Works during both static rendering (build time) and dynamic rendering (request time), persists across deployments
  • Full Route Cache: Only applies to statically rendered routes; dynamic routes bypass it entirely

How they interact:

  • Revalidating or opting out of the Data Cache invalidates the Full Route Cache (because rendered output depends on data)
  • Invalidating the Full Route Cache does not affect the Data Cache (asymmetric relationship)

This enables hybrid caching: you can dynamically render a route while still using cached data for some components, or render statically while fetching specific data at request time.

Sources:

99% confidence
A

The Router Cache stores static routes for 5 minutes by default.

This applies when using default prefetching (prefetch={null} or unspecified on <Link> components). The cache is stored in the browser's temporary memory and persists across navigation, but is cleared on page refresh.

The duration can be customized using the experimental staleTimes configuration option.

Sources:

99% confidence
A

The Router Cache in Next.js handles dynamic routes differently based on how they're prefetched:

Default Prefetching (prefetch={null} or unspecified):

  • Dynamic routes: Not cached
  • Static routes: 30 seconds

Full Prefetching (prefetch={true} or router.prefetch()):

  • Dynamic routes: 5 minutes
  • Static routes: 5 minutes

The Router Cache is a client-side in-memory cache that stores the React Server Component Payload. It persists across navigations but is cleared on page refresh.

Sources:

99% confidence
A

router.refresh() makes a new request to the server, re-fetches data requests, and re-renders Server Components. The client merges the updated React Server Component payload without losing unaffected client-side React state (e.g., useState) or browser state (e.g., scroll position).

What gets refreshed:

  • Server Components are re-rendered with fresh data
  • Data requests are re-fetched from the server

What is preserved:

  • Client-side React state (useState, etc.)
  • Browser state (scroll position)
  • Unaffected client components

Important caveat:
refresh() may reproduce the same result if fetch requests are cached. Other Dynamic APIs like cookies and headers could also change the response.

Usage:

'use client'
import { useRouter } from 'next/navigation'

export default function Page() {
  const router = useRouter()
  
  return (
    <button onClick={() => router.refresh()}>
      Refresh
    </button>
  )
}

Sources:

99% confidence
A

To opt out of the Data Cache for a specific fetch request in Next.js, use the cache: 'no-store' option:

fetch('https://api.example.com/data', { cache: 'no-store' })

This fetches data from the remote server on every request, bypassing Next.js's Data Cache completely.

Alternative method:

fetch('https://api.example.com/data', { next: { revalidate: 0 } })

Setting revalidate: 0 also prevents caching.

Important: Don't combine conflicting options like { revalidate: 3600, cache: 'no-store' } - they'll be ignored and trigger a warning in development.

Sources:

99% confidence
A

Use the next.tags option in fetch to tag requests for cache invalidation:

fetch('https://api.example.com/posts', { 
  next: { tags: ['posts'] } 
})

You can assign multiple tags to a single request:

fetch('https://api.example.com/posts/1', { 
  next: { tags: ['posts', 'post-1'] } 
})

Limitations:

  • Maximum tag length: 256 characters
  • Maximum number of tags: 128 per request

To invalidate the cache, use revalidateTag in Server Actions or Route Handlers:

'use server'
import { revalidateTag } from 'next/cache'

export async function updatePost() {
  await savePost()
  revalidateTag('posts', 'max')
}

The recommended profile="max" marks data as stale while continuing to serve cached content until the next page visit fetches fresh data.

Sources:

99% confidence
A

unstable_cache allows you to cache the results of expensive operations (like database queries or API calls) and reuse them across multiple requests.

Primary purpose: Reduce performance overhead by avoiding redundant execution of expensive async operations. The cached results persist across requests and deployments using Next.js' built-in Data Cache.

Basic usage:

import { unstable_cache } from 'next/cache';

const getCachedUser = unstable_cache(
  async (id) => getUser(id),
  ['my-app-user']
);

// Later in your component
const user = await getCachedUser(userID);

API signature:

unstable_cache(fetchData, keyParts, options)
  • fetchData: Async function that returns a Promise with data to cache
  • keyParts: Array of additional keys for cache identification
  • options: Configuration for revalidation and tags

Critical limitation: Cannot access dynamic data sources (headers, cookies) inside the cached function. These must be accessed outside and passed as arguments.

Sources:

99% confidence
A

Next.js App Router provides multiple ways to cache database queries with Prisma and Drizzle:

1. React's cache() Function (Request-Level Memoization)

Wrap your database queries to deduplicate requests within a single render:

import { cache } from 'react'
import { prisma } from '@/lib/prisma'

export const getUser = cache(async (id: string) => {
  return await prisma.user.findUnique({ where: { id } })
})

This prevents the same query from running multiple times during one request, but does not persist across requests.

2. unstable_cache() (Data Cache - Persists Across Requests)

For persistent caching across requests and deployments:

import { unstable_cache } from 'next/cache'
import { prisma } from '@/lib/prisma'

export const getUser = unstable_cache(
  async (id: string) => {
    return await prisma.user.findUnique({ where: { id } })
  },
  ['user-cache'], // cache key
  {
    revalidate: 3600, // revalidate every hour
    tags: ['users'] // for cache invalidation
  }
)

Despite the name, unstable_cache is production-ready and widely used.

3. use cache Directive (Next.js 15+)

The newer approach for caching functions:

'use cache'

import { db } from '@/lib/db'

export async function getProducts() {
  return await db.query.products.findMany()
}

Or inline:

import { db } from '@/lib/db'

export async function getProducts() {
  'use cache'
  return await db.query.products.findMany()
}

4. Revalidation

Invalidate caches after mutations:

import { revalidatePath, revalidateTag } from 'next/cache'

// Revalidate specific path
revalidatePath('/users')

// Revalidate by cache tag
revalidateTag('users')

5. Opt-Out of Caching

Force dynamic rendering for always-fresh data:

export const dynamic = 'force-dynamic' // route segment config

Key Difference: cache() deduplicates within a request, while unstable_cache and use cache persist across requests.

Sources:

99% confidence

metadata_seo

10 questions
A

Export a Metadata object from a layout.js or page.js file:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Page Title',
  description: 'Page description',
}

export default function Page() {
  return <div>Your content</div>
}

Or in JavaScript:

export const metadata = {
  title: 'Page Title',
  description: 'Page description',
}

export default function Page() {
  return <div>Your content</div>
}

Key requirements:

  • Export must be from a Server Component (not Client Component)
  • Only in layout.js/page.js files (not regular components)
  • Cannot export both metadata object and generateMetadata function from the same file
  • Use static metadata export when values don't depend on runtime information
  • For dynamic metadata based on route params or external data, use generateMetadata() instead

Available fields: title, description, openGraph, twitter, robots, icons, viewport, alternates, and more.

Sources:

99% confidence
A

Export a generateMetadata function from a Server Component (page or layout) to dynamically generate metadata. The function receives route params and searchParams as promises and returns a Metadata object.

Basic syntax:

import type { Metadata } from 'next'

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: 'My Page',
    description: 'Page description'
  }
}

With dynamic route parameters:

type Props = {
  params: Promise<{ id: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { id } = await params
  const product = await fetch(`https://api.example.com/products/${id}`)
    .then(res => res.json())
  
  return {
    title: product.title,
    description: product.description,
  }
}

Accessing parent metadata:

export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  const previousImages = (await parent).openGraph?.images || []
  
  return {
    openGraph: {
      images: ['/new-image.jpg', ...previousImages],
    },
  }
}

Key rules:

  • Only works in Server Components (not Client Components)
  • Cannot export both metadata object and generateMetadata from same file
  • fetch requests are automatically memoized across the rendering tree
  • searchParams only available in page.js, not layouts
  • File-based metadata (like opengraph-image.jpg) overrides generateMetadata

Sources:

99% confidence
A

Yes, generateMetadata is designed to be an async function and fully supports async/await.

The function signature is:

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata>

You can use await inside to fetch data, access route parameters, or extend parent metadata:

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  // Await route params
  const { id } = await params
  
  // Await fetch request
  const product = await fetch(`https://api.example.com/${id}`).then(res => res.json())
  
  return {
    title: product.title,
    description: product.description
  }
}

Key points:

  • The function must be async and return Promise<Metadata>
  • Fetch requests are automatically memoized across generateMetadata, pages, and layouts
  • Only supported in Server Components
  • In Next.js 15+, params and searchParams are Promises that must be awaited

Sources:

99% confidence
A

Yes, fetch requests in generateMetadata are automatically deduplicated in Next.js.

fetch requests inside generateMetadata are automatically memoized for the same data across generateMetadata, generateStaticParams, Layouts, Pages, and Server Components. This means if you make identical fetch requests in both your generateMetadata function and your page component, Next.js will only execute the network request once.

For non-fetch data fetching (like direct database calls), you can use React's cache function to achieve the same deduplication:

import { cache } from 'react'

export const getPost = cache(async (slug: string) => {
  const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
  return res
})

export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug) // Called here
  return { title: post.title }
}

export default async function Page({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug) // And here - but executed only once
  return <div>{post.title}</div>
}

Sources:

99% confidence
A

Next.js streams metadata separately for regular browsers but inlines it for HTML-limited bots.

For regular browsers (streaming):

  • Next.js sends the initial UI immediately without waiting for generateMetadata() to complete
  • When generateMetadata() resolves, metadata tags are appended to the <body> tag
  • Improves TTFB and LCP by not blocking the initial page render

For HTML-limited bots (inline, blocking):

  • Next.js detects bots that cannot execute JavaScript (e.g., facebookexternalhit) via User Agent
  • For these bots, page rendering blocks until generateMetadata() completes
  • Metadata is included in the <head> tag before sending HTML
  • Ensures crawlers and social media platforms can read metadata

Customization:
You can configure which bots receive blocking behavior using the htmlLimitedBots option in next.config.js.

Version note: This streaming behavior was introduced in Next.js 15.2. Previously, all requests blocked on metadata generation.

Sources:

99% confidence
A

Yes, you can use both redirect() and notFound() inside generateMetadata in Next.js.

The official Next.js documentation explicitly states: "The redirect() and notFound() Next.js methods can also be used inside generateMetadata."

This allows you to handle conditional routing based on metadata generation logic - for example, redirecting when a resource has moved or showing a 404 when the requested content doesn't exist, all before the metadata is generated.

Example:

export async function generateMetadata({ params }) {
  const post = await getPost(params.id);
  
  if (!post) {
    notFound(); // Trigger 404 page
  }
  
  if (post.redirectUrl) {
    redirect(post.redirectUrl); // Redirect to new location
  }
  
  return {
    title: post.title,
  };
}

Sources:

99% confidence
A

Next.js provides two approaches for defining metadata:

Config-Based Metadata

Defined by exporting a static metadata object or dynamic generateMetadata function from layout.js or page.js files:

// Static metadata object
export const metadata = {
  title: 'My Page',
  description: 'Page description',
}

// OR dynamic metadata function
export async function generateMetadata({ params }) {
  return {
    title: params.slug,
  }
}

This approach is code-based and supported only in Server Components.

File-Based Metadata

Defined by adding special files to route segments using naming conventions:

  • favicon.ico, icon.jpg, apple-icon.jpg - for icons
  • opengraph-image.jpg, twitter-image.jpg - for social media images
  • robots.txt - for crawler instructions
  • sitemap.xml - for site structure

These files are placed in app directory folders and automatically generate corresponding <head> elements.

Key Difference

File-based metadata has higher priority and will override config-based metadata. File-based uses naming conventions for automatic generation, while config-based requires explicit code exports. Choose file-based for static assets like images/icons, and config-based for dynamic metadata that depends on runtime information.

Sources:

99% confidence
A

Perfect! I found the authoritative answer.

File-based metadata has higher priority. The opengraph-image.jpg file will override any metadata.openGraph configuration.

According to the official Next.js documentation, the explicit rule is:

"File-based metadata has the higher priority and will override any config-based metadata."

This means if you have both:

  • An opengraph-image.jpg file in a route segment
  • An openGraph.images property in your metadata export

The opengraph-image.jpg file takes precedence and will be used as the Open Graph image, ignoring the config-based metadata.openGraph settings.

Priority Order:

  1. File-based metadata (opengraph-image.jpg) - Highest priority
  2. Config-based metadata (metadata.openGraph) - Lower priority

Sources:

99% confidence
A

Generate dynamic OpenGraph images in Next.js using ImageResponse from next/og. There are two approaches:

Approach 1: File-based (Recommended)

Create an opengraph-image.tsx file in any route segment:

// app/opengraph-image.tsx
import { ImageResponse } from 'next/og'

export const alt = 'My Site'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'

export default async function Image() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        Hello World!
      </div>
    ),
    { ...size }
  )
}

For dynamic routes with parameters:

// app/posts/[slug]/opengraph-image.tsx
export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  
  return new ImageResponse(
    (
      <div style={{ /* styles */ }}>
        Post: {slug}
      </div>
    ),
    { width: 1200, height: 630 }
  )
}

Approach 2: Route Handler

Create an API route that returns ImageResponse:

// app/api/og/route.tsx
import { ImageResponse } from 'next/og'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const title = searchParams.get('title') || 'Default Title'

  return new ImageResponse(
    (
      <div style={{ /* styles */ }}>
        {title}
      </div>
    ),
    { width: 1200, height: 630 }
  )
}

Key Constraints

  • Only flexbox layout supported (no CSS Grid)
  • Subset of CSS properties only
  • 500KB maximum bundle size
  • Font formats: ttf, otf, or woff (ttf/otf preferred)
  • Images are statically optimized at build time unless using Dynamic APIs

Constructor Options

new ImageResponse(element, {
  width: 1200,
  height: 630,
  emoji: 'twemoji', // or 'blobmoji', 'noto', 'openmoji'
  fonts: [{ name: 'MyFont', data: fontBuffer, weight: 400 }],
  status: 200,
  headers: { 'Cache-Control': 'public, max-age=31536000' }
})

Sources:

99% confidence
A

No. ImageResponse in Next.js only supports the Edge Runtime. The default Node.js runtime will not work.

When using ImageResponse from next/og, you must explicitly set the runtime to edge:

import { ImageResponse } from 'next/og'

export const runtime = 'edge'

export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        Hello World!
      </div>
    ),
  )
}

Why Edge Runtime only:

  • ImageResponse uses the Edge Runtime for performance optimization
  • Next.js automatically adds correct headers to cached images at the edge
  • Helps improve performance and reduce recomputation

Sources:

99% confidence

middleware_authentication

9 questions
A

Yes, you CAN check user authentication in layout.tsx, BUT you should NOT rely on it for security.

The Problem:
Layouts in Next.js App Router use partial rendering and don't re-render on navigation. This means authentication checks in layouts only run once on initial page load, not on subsequent route changes. Users could navigate to protected routes without the auth check running again.

What Next.js Recommends Instead:

  1. Fetch user data in layouts - It's fine to call getUser() or similar in layouts to get session data

  2. Do auth checks closer to data - Perform actual authorization checks in:

    • Your Data Access Layer (DAL)
    • Page components
    • Leaf components that consume the data
    • Server Actions
  3. Don't return null from layouts for auth - Returning null from a layout when unauthorized doesn't prevent access to nested routes or Server Actions (multiple entry points exist)

Example Pattern:

// layout.tsx - Fetch user, don't check auth
export default async function Layout({ children }) {
  const user = await getUser() // Just fetch
  return <div>{children}</div>
}

// page.tsx or component - Check auth
export default async function Page() {
  const data = await getData() // getData() checks auth internally
  return <div>{data}</div>
}

Sources:

99% confidence
A

Layouts don't re-render on navigation in Next.js App Router due to partial rendering. This means any authentication checks you place in a layout component will only execute once when the layout first mounts - they won't re-run when users navigate between different routes within that layout.

The security risk: If you rely solely on layout-level auth checks, a user's session won't be verified on every route change. An expired or invalidated session could persist across navigation, allowing unauthorized access to protected routes.

What to do instead:

  • Perform auth checks close to your data source (in API routes, Server Actions, or data-fetching functions)
  • Add auth checks in the components that will be conditionally rendered based on permissions
  • Use layouts only for UI structure, not security enforcement
// ❌ Don't rely on this alone
// app/dashboard/layout.tsx
export default async function DashboardLayout({ children }) {
  const session = await getSession();
  if (!session) redirect('/login');
  return <div>{children}</div>;
}

// ✅ Do this instead
// app/dashboard/page.tsx or data access layer
export default async function DashboardPage() {
  const session = await getSession();
  if (!session) redirect('/login');
  
  const data = await fetchProtectedData(session);
  return <Dashboard data={data} />;
}

Sources:

99% confidence
A

In NextAuth.js v5 (Auth.js), you export an auth() function from your configuration file and import it directly wherever you need to access the session—no need to pass authOptions anymore.

Setup (auth.ts or auth.js):

import NextAuth from "next-auth"

export const { auth, handlers, signIn, signOut } = NextAuth({
  providers: [/* your providers */],
})

Usage in Server Components:

import { auth } from "@/auth"

export default async function Page() {
  const session = await auth()
  return <p>Welcome {session?.user.name}!</p>
}

Usage in API Routes (App Router):

import { auth } from "@/auth"

export const GET = auth((req) => {
  if (req.auth) {
    // User is authenticated
    return Response.json({ data: "protected data" })
  }
  return Response.json({ message: "Not authenticated" }, { status: 401 })
})

Usage in Middleware:

export { auth as middleware } from "@/auth"

Key Difference from v4:

  • v4: await getServerSession(authOptions) - required passing config
  • v5: await auth() - config is already bound to the function

The auth() function is now a universal replacement for getServerSession, getToken, and withAuth depending on the context.

Sources:

99% confidence
A

Based on the official Auth.js documentation, NextAuth.js middleware runs exclusively in the Edge runtime, not Node.js runtime.

Key Facts:

  1. Next.js Middleware Requirement: Next.js middleware (including NextAuth.js middleware) only runs in the Edge runtime by default. This is a Next.js platform constraint, not a NextAuth.js limitation.

  2. Edge-Only Execution: When you use NextAuth.js middleware in your middleware.ts file, it executes in the Edge runtime automatically.

  3. Compatibility Strategy: To make NextAuth.js work in Edge runtime, you must:

    • Use JWT session strategy (not database sessions)
    • Separate edge-compatible config from Node.js-dependent features (like database adapters)
    • Create an auth.config.ts with edge-compatible options only
    • Import this config in your middleware file
  4. Database Adapter Limitation: Database adapters (MongoDB, Prisma, etc.) require Node.js APIs and cannot run in Edge runtime, so they must be excluded from middleware configuration.

Code Pattern:

// auth.config.ts (edge-compatible)
export default {
  providers: [...],
  session: { strategy: "jwt" }
  // No adapter here
}

// middleware.ts
import authConfig from "./auth.config"
export { auth as middleware } from "@auth/nextjs"

The middleware itself doesn't support dual runtime execution - it's Edge-only by architectural design of Next.js middleware.

Sources:

99% confidence
A

To protect routes with middleware authentication in Next.js, create a middleware.ts (or .js) file in the root of your project (same level as pages or app, or inside src if you use that structure).

Basic Implementation:

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Check authentication (e.g., session cookie)
  const token = request.cookies.get('auth-token')
  
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  
  return NextResponse.next()
}

// Specify which routes to protect
export const config = {
  matcher: ['/dashboard/:path*', '/account/:path*']
}

Protect API Routes:

import { NextRequest } from 'next/server'

export const config = {
  matcher: '/api/:function*',
}

export function middleware(request: NextRequest) {
  const isAuthenticated = request.cookies.get('session')
  
  if (!isAuthenticated) {
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}

Matcher Patterns:

The matcher config targets specific routes:

  • /dashboard/:path* - Protects all dashboard routes
  • ['/about/:path*', '/profile/:path*'] - Multiple route patterns
  • /((?!api|_next/static|_next/image|.*\\.png$).*) - Everything except specified patterns

Key Points:

  • Middleware executes before cached content and routes are matched
  • Place middleware.ts at project root (or in src/)
  • Use matcher to target specific routes that need protection
  • Check cookies/headers for authentication tokens
  • Redirect unauthenticated users or return 401 responses

Important: Extensive session management should be handled by dedicated authentication services or Route Handlers, not solely in middleware.

Sources:

99% confidence
A

Based on the official Next.js documentation, here's how to implement role-based access control (RBAC) in Next.js middleware:

Implementation Pattern

Create a middleware.ts file at your project root (same level as app or pages):

import { NextResponse, NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // 1. Extract user session/token (from cookie, header, or JWT)
  const token = request.cookies.get('auth-token')?.value
  
  // 2. Decode and get user role
  const userRole = getUserRoleFromToken(token) // Your auth logic
  
  // 3. Define route-role mappings
  const path = request.nextUrl.pathname
  
  if (path.startsWith('/admin')) {
    if (userRole !== 'admin') {
      return NextResponse.redirect(new URL('/unauthorized', request.url))
    }
  }
  
  if (path.startsWith('/dashboard')) {
    if (!['admin', 'user'].includes(userRole)) {
      return NextResponse.redirect(new URL('/login', request.url))
    }
  }
  
  return NextResponse.next()
}

// 4. Specify which routes to protect
export const config = {
  matcher: ['/admin/:path*', '/dashboard/:path*']
}

Key Components

1. Matcher Configuration - Specify which routes trigger middleware:

export const config = {
  matcher: ['/admin/:path*', '/api/admin/:path*']
}

2. Session/Token Extraction - Read from cookies, headers, or JWT:

const token = request.cookies.get('session')?.value
const authHeader = request.headers.get('authorization')

3. Role Validation - Check role and return appropriate response:

if (userRole !== 'admin') {
  return NextResponse.redirect(new URL('/unauthorized', request.url))
}

// Or return JSON for API routes
return Response.json(
  { success: false, message: 'insufficient permissions' },
  { status: 403 }
)

4. NextResponse Actions:

  • NextResponse.redirect() - Redirect to different URL
  • NextResponse.rewrite() - Display different URL content
  • NextResponse.next() - Continue to requested route
  • Response.json() - Return JSON response

Execution Timing

Middleware runs before routes are rendered and after next.config.js redirects, making it ideal for authentication/authorization checks.

Sources:

99% confidence
A

Auth.js v5 Setup in Next.js 14/15

1. Install Auth.js

npm install next-auth@beta

2. Generate Secret

npx auth secret

Or manually:

openssl rand -base64 33

3. Environment Variables

Create .env.local:

AUTH_SECRET="your-generated-secret"
AUTH_GITHUB_ID="your-github-client-id"
AUTH_GITHUB_SECRET="your-github-client-secret"

Auth.js automatically infers environment variables prefixed with AUTH_.

4. Create Auth Configuration

Create auth.ts in project root:

import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [GitHub],
})

5. Create API Route Handler

Create app/api/auth/[...nextauth]/route.ts:

import { handlers } from "@/auth"
export const { GET, POST } = handlers

Requirements:

  • Minimum Next.js version: 14.0
  • When deploying behind reverse proxy, set AUTH_TRUST_HOST=true

Sources:

99% confidence

advanced_routing

9 questions
A

Parallel Routes allow you to render multiple pages simultaneously or conditionally within the same layout using named slots defined with the @folder convention.

How they work:

Named slots are created using folders prefixed with @ (e.g., @analytics, @team). These slots are passed as props to the shared parent layout and can render different content side-by-side. Critically, slots are not route segments and do not affect the URL structure—/@analytics/views renders at /views.

File structure example:

app/
  @analytics/
    page.tsx
  @team/
    page.tsx
  layout.tsx

Layout receives slots as props:

export default function Layout({
  children,
  analytics,
  team,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {children}
      {analytics}
      {team}
    </>
  )
}

When to use them:

  • Dashboards with multiple independent sections
  • Conditional rendering based on user roles/permissions
  • Tabbed interfaces within the same layout
  • Modals with deep linking (combined with Intercepting Routes)
  • Independent loading/error states for each section (each route can stream independently)

Key feature: Each slot can have its own default.js file to render as a fallback for unmatched slots during initial load or full-page reload, preventing 404 errors.

Sources:

99% confidence
A

Create a slot in Next.js Parallel Routes by using the @folder naming convention.

Steps:

  1. Create a folder with @ prefix - Name it @slotname (e.g., @analytics, @team, @dashboard)
  2. Place it at the same level as other route segments - The folder sits alongside other app directory folders
  3. Add a page.tsx inside - Create content that will render in that slot
  4. Access as props in parent layout - The slot name (without @) becomes a prop in the shared layout

Example Structure:

app/
├── layout.tsx          # Receives slots as props
├── page.tsx           # children prop
├── @analytics/        # Slot named "analytics"
│   └── page.tsx
└── @team/             # Slot named "team"
    └── page.tsx

Parent Layout Implementation:

export default function Layout({
  children,
  analytics,
  team,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {children}
      {analytics}
      {team}
    </>
  )
}

Important: Slots do NOT affect the URL structure. A file at /@analytics/views will have the URL /views.

Sources:

99% confidence
A

Parallel Routes work only in layout.tsx, not in page.tsx.

Parallel Routes are defined using named slots (folders with the @folder convention) and are passed as props to the shared parent layout component. The layout receives these slots as props and renders them.

Example:

// app/layout.tsx
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}

The @analytics and @team folder slots become props on the layout component. The children prop is an implicit slot representing the page.tsx content. Slots do not affect the URL structure.

Sources:

99% confidence
A

Intercepting Routes allow you to load a route from another part of your application within the current layout. This lets you display route content without switching the user's context—for example, showing a photo in a modal while staying on a feed page.

How They Work

Intercepting Routes distinguish between two navigation types:

Soft Navigation (client-side): When users click a link within your app, the intercepted route renders in the current layout. The URL updates but the page doesn't reload.

Hard Navigation (direct access): When users navigate directly via URL, refresh, or bookmark, the full page renders instead of the intercepted version.

Convention

Use relative path syntax with parentheses in folder names:

  • (.) — intercept routes at the same level
  • (..) — intercept routes one level up
  • (..)(..) — intercept routes two levels up
  • (...) — intercept routes from the app root

Important: The convention is based on route segments, not the file-system structure. Parallel route slots (like @modal) don't count toward the segment calculation.

Example Structure

app/
  feed/
    @modal/
      (.)photo/
        [id]/
          page.tsx    # Intercepted route (shows as modal)
    page.tsx          # Feed page
  photo/
    [id]/
      page.tsx        # Full photo page (direct access)

In this example, clicking a photo link from /feed shows the modal via interception, while navigating directly to /photo/123 shows the full page.

Primary Use Case

Intercepting Routes work with Parallel Routes to create modals that:

  • Are shareable via URL
  • Preserve context on page refresh
  • Close on back navigation (instead of going to previous route)
  • Reopen on forward navigation

Sources:

99% confidence
A

The (.) notation in Next.js Intercepting Routes matches segments on the same level in the route hierarchy.

How It Works

When you create a folder named (.)segmentName, it intercepts navigation to the sibling route segmentName at the same level. For example:

app/
  folder/
    page.tsx           # /folder
    (.)photo/          # Intercepts /folder/photo
      page.tsx
    photo/
      page.tsx         # /folder/photo (actual route)

When navigating from /folder to /folder/photo, the (.)photo/page.tsx will be shown instead of the actual photo/page.tsx, while maintaining the URL as /folder/photo.

All Intercepting Route Conventions

  • (.) - match segments on the same level
  • (..) - match segments one level above
  • (..)(..) - match segments two levels above
  • (...) - match segments from the root app directory

Important Note

The (..) convention is based on route segments, not the file-system. Route groups (folders in (parentheses)) and parallel route slots (folders prefixed with @) don't count as segments.

Sources:

99% confidence
A

Combine Parallel Routes and Intercepting Routes by creating a parallel slot (e.g., @auth) with an intercepting route inside it that renders a modal, while maintaining a separate dedicated page for direct access.

Folder Structure

app/
├── layout.tsx              # Root layout that renders both @auth slot and children
├── @auth/                  # Parallel route slot
│   ├── (.)login/          # Intercepting route (intercepts /login)
│   │   └── page.tsx       # Modal version of login
│   ├── default.tsx        # Returns null when slot is inactive
│   └── page.tsx           # Returns null on root route
└── login/
    └── page.tsx           # Dedicated /login page (direct access)

Implementation Steps

1. Create the dedicated page (app/login/page.tsx):

import { Login } from '@/app/ui/login'

export default function Page() {
  return <Login />
}

2. Create the parallel slot default (app/@auth/default.tsx):

export default function Default() {
  return null
}

3. Create the intercepting route (app/@auth/(.)login/page.tsx):

import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}

4. Configure parent layout (app/layout.tsx):

export default function Layout({
  auth,
  children,
}: {
  auth: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <>
      <nav>
        <Link href="/login">Open modal</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}

5. Implement modal with close (app/ui/modal.tsx):

'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()

  return (
    <>
      <button onClick={() => router.back()}>Close modal</button>
      <div>{children}</div>
    </>
  )
}

6. Handle slot closure (app/@auth/page.tsx):

export default function Page() {
  return null
}

How It Works

  • Client-side navigation to /login: Intercepts and renders modal in @auth slot
  • Direct access to /login (page refresh, direct URL): Renders dedicated page
  • Back navigation: Closes modal using router.back()
  • Navigate away: Slot renders null, modal disappears

Key insight: The (.) matcher means the route is one segment level higher, not filesystem level. Since slots (@auth) don't count as segments, (.)login intercepts /login even though it's two filesystem levels deep.

Sources:

99% confidence
A

When you refresh a page with an Intercepting Route modal in Next.js, the modal closes and the full page renders instead.

On hard navigation (page refresh, direct URL access, or browser back/forward), Next.js does NOT intercept the route. The user sees the complete page at that route, not the modal overlay.

On soft navigation (client-side navigation using <Link> or router.push()), Next.js intercepts the route and displays it as a modal.

Example:

  • You click a photo thumbnail → Opens as modal (intercepted route)
  • You refresh the page → Modal closes, full photo page renders (no interception)
  • You share the URL → Recipient sees full photo page (no interception)

This design preserves shareable URLs while maintaining the modal UX during app navigation. The intercepted route still exists as a standalone page, so refreshing doesn't break the application—it just changes the presentation.

Sources:

99% confidence
A

To close a modal created with Intercepting Routes in Next.js, use router.back() or navigate with a Link component to a route where the modal slot returns null.

Method 1: Using router.back()

'use client'
import { useRouter } from 'next/navigation'

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()
  
  return (
    <>
      <button onClick={() => router.back()}>Close modal</button>
      <div>{children}</div>
    </>
  )
}

Method 2: Using Link component

import Link from 'next/link'

export function Modal({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Link href="/">Close modal</Link>
      <div>{children}</div>
    </>
  )
}

Required: Create a default.js file in your slot

To ensure the modal closes properly when navigating to routes that don't match the intercepting route, add a default.js file in your slot directory that returns null:

// app/@modal/default.js
export default function Default() {
  return null
}

This prevents the modal from remaining visible after navigation to unmatched routes.

Sources:

99% confidence
A

default.js is a fallback file for Parallel Routes that renders when Next.js cannot recover a slot's active state after a full-page load (hard navigation).

When You Need It:

You need default.js when using Parallel Routes if:

  1. Slots have mismatched routes - If one slot has a page for a route but another slot doesn't, the slot without a matching page needs default.js on refresh

    • Example: @team has /settings but @analytics doesn't → @analytics needs default.js
  2. For EVERY named slot - All named slots (@folder pattern) require default.js to prevent errors during hard navigation

  3. For the implicit children slot - Even the default slot needs default.js to prevent 404 errors when Next.js can't recover the parent page's active state

What Happens Without It:

  • Named slots without default.js → Error
  • children slot without default.js → 404 page

Common Pattern:
For modals or conditional UI, return null to hide the slot when inactive:

// app/@modal/default.tsx
export default function Default() {
  return null
}

To Preserve 404 Behavior:

import { notFound } from 'next/navigation'

export default function Default() {
  notFound()
}

Sources:

99% confidence

image_optimization

9 questions
A

Yes. The next/image component lazy loads images by default.

The loading prop defaults to "lazy", which defers loading until the image reaches a calculated distance from the viewport using native browser lazy loading.

To override this behavior:

  • Use loading="eager" to load immediately
  • Use the priority prop (automatically disables lazy loading for critical above-the-fold images)

Example:

// Default behavior - lazy loaded
<Image src="/photo.jpg" alt="Photo" width={500} height={300} />

// Override - load immediately
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} loading="eager" />

// Priority image - also eager loaded
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />

Sources:

99% confidence
A

Use the next/image component from Next.js to get automatic image optimization including format conversion (WebP/AVIF), responsive sizing, and lazy loading.

Basic Implementation

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="/profile.png"
      width={500}
      height={500}
      alt="Picture of the author"
    />
  )
}

Required Props

  • src: Image path (local or remote URL)
  • alt: Accessibility text
  • width and height: Image dimensions in pixels (unless using fill)

Key Optimization Props

sizes - Critical for responsive images. Tells the browser which image size to load at different breakpoints:

<Image
  src="/hero.png"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  alt="Hero image"
/>

quality - Compression level from 1-100 (default: 75)

priority - Set to true for above-the-fold images to disable lazy loading and preload them:

<Image
  src="/hero.png"
  width={1200}
  height={600}
  priority
  alt="Hero"
/>

placeholder="blur" - Shows blur-up effect while loading (requires blurDataURL for remote images)

Remote Images Configuration

Add allowed domains to next.config.js:

module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        pathname: '/images/**',
      },
    ],
  },
}

Fill Mode for Unknown Dimensions

When dimensions are unknown, use fill (parent must have position: relative):

<div style={{ position: 'relative', width: '100%', height: '400px' }}>
  <Image
    src="/image.png"
    fill
    style={{ objectFit: 'cover' }}
    alt="Cover image"
  />
</div>

Automatic Optimizations

Next.js automatically:

  • Serves modern formats (WebP, AVIF)
  • Generates responsive image sizes
  • Lazy loads images when they enter viewport
  • Prevents layout shift
  • Optimizes images on-demand (not at build time)

Sources:

99% confidence
A

The default loading strategy for next/image in Next.js is "lazy".

Images with loading="lazy" defer loading until they reach a calculated distance from the viewport. This is the default behavior when the loading prop is not specified.

You can override this by setting loading="eager" to load the image immediately, or by using the priority prop (which automatically sets eager loading and adds preload tags for critical images like LCP elements).

Sources:

99% confidence
A

To prevent Cumulative Layout Shift (CLS) with next/image in Next.js, use one of these approaches:

1. Specify width and height (recommended for static images)

Provide explicit width and height props - these infer the aspect ratio so browsers can reserve space before the image loads:

import Image from 'next/image'

<Image
  src="/photo.jpg"
  width={500}
  height={300}
  alt="Description"
/>

2. Use static imports (automatic dimensions)

For local images, import them directly. Next.js automatically determines width and height from the file:

import Image from 'next/image'
import photo from '../public/photo.jpg'

<Image
  src={photo}
  alt="Description"
/>

3. Use fill prop for responsive containers

When dimensions are unknown or for responsive layouts, use fill with a positioned parent:

<div style={{ position: 'relative', width: '100%', height: '400px' }}>
  <Image
    src="/photo.jpg"
    fill
    sizes="100vw"
    alt="Description"
  />
</div>

The parent must have position: relative, fixed, or absolute.

4. Add sizes prop for responsive images

When using fill or responsive layouts, add the sizes prop to prevent unnecessarily large downloads:

<Image
  src="/photo.jpg"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  alt="Description"
/>

Key principle: The browser needs aspect ratio information before the image loads. Providing width and height (or using static imports) allows the browser to reserve the correct space, preventing layout shift.

Sources:

99% confidence
A

Use the fill prop when:

  1. You don't know the image dimensions ahead of time - The fill prop is designed for cases where height and width are unknown.

  2. The image should fill its parent container - fill makes the image expand to match the size of its parent element, useful for responsive layouts.

  3. You want the parent to control sizing - Instead of setting explicit width/height on the image, you control dimensions via CSS on the parent container.

Use width and height when:

  • You know the image dimensions - These props are required unless using fill or importing a static image. They prevent layout shift by establishing the aspect ratio before the image loads.

Key requirements when using fill:

<div style={{ position: 'relative', width: '100%', height: '400px' }}>
  <Image
    fill
    src="/my-image.png"
    alt="Description"
    sizes="100vw"
    style={{ objectFit: 'cover' }}
  />
</div>
  • Parent must have position: relative, fixed, or absolute
  • Use the sizes prop to prevent downloading unnecessarily large images
  • Use object-fit (cover, contain, fill) to control how the image fills the space

Sources:

99% confidence
A

The sizes prop tells the browser what width the image will be at different viewport sizes, enabling it to select the optimal image from the generated srcset.

Usage

The sizes prop accepts a media query string that maps viewport widths to expected image display widths:

import Image from 'next/image'

<Image
  fill
  src="/example.png"
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

This reads as:

  • Below 768px: image takes 100% of viewport width
  • Between 768px-1200px: image takes 50% of viewport width
  • Above 1200px: image takes 33% of viewport width

When to Use

Use sizes when:

  • The image uses the fill prop (layout fills parent container)
  • CSS makes the image responsive (not fixed width)

Important: If omitted, browser assumes 100vw (full viewport width), causing unnecessarily large images to download on smaller screens.

How It Works

The sizes prop affects srcset generation:

  • Without sizes: Next.js generates density descriptors (1x, 2x) for fixed-size images
  • With sizes: Next.js generates width descriptors (640w, 750w, 828w, 1080w, 1200w, 1920w, 2048w, 3840w) for responsive images

Common Patterns

// Full width on mobile, half width on desktop
sizes="(max-width: 768px) 100vw, 50vw"

// Sidebar image: full width mobile, fixed 300px desktop
sizes="(max-width: 768px) 100vw, 300px"

// Grid layout: varies by breakpoint
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"

Sources:

99% confidence
A

To enable remote images in Next.js, configure remotePatterns in your next.config.js or next.config.ts file.

Configuration

Add the images.remotePatterns array to specify allowed external image URLs:

// next.config.ts
import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: '',
        pathname: '/images/**',
      },
    ],
  },
}

export default config

remotePatterns Properties

  • protocol: 'http' or 'https' (required for security)
  • hostname: Domain name, supports wildcards (*.example.com)
  • port: Port number or empty string for any port
  • pathname: Path pattern, supports * (single segment) and ** (multiple segments)
  • search: Query string pattern (optional)

Wildcard Examples

remotePatterns: [
  {
    protocol: 'https',
    hostname: '**.example.com', // All subdomains
    pathname: '/images/**',      // All paths under /images
  },
]

Security Note

Be specific with patterns. Omitting protocol, port, pathname, or search implies wildcard **, which can allow malicious optimization of unintended URLs.

Deprecated Alternative

The domains array (deprecated in Next.js 14) is less secure:

images: {
  domains: ['example.com'], // Less specific, not recommended
}

Use remotePatterns instead for granular control.

Sources:

99% confidence
A

Next.js next/image automatically serves WebP by default.

Default Configuration

formats: ['image/webp']

AVIF Support

AVIF is supported but not enabled by default. You must explicitly configure it in next.config.js:

module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

With this configuration, Next.js will prefer AVIF for browsers that support it (via the Accept header), falling back to WebP, then the original format.

How Format Selection Works

Next.js automatically detects the browser's supported formats via the request's Accept header. The first matching format in the array is served. If no supported format matches or the image is animated, the original format is served.

Performance Trade-offs

  • WebP: Faster encoding, good compression (recommended default)
  • AVIF: 20% smaller file sizes than WebP, but 50% longer to encode

Sources:

99% confidence
A

Configure custom image breakpoints in next.config.js using the deviceSizes property:

module.exports = {
  images: {
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  }
}

What deviceSizes does: Specifies device width breakpoints used when the next/image component has a sizes prop to ensure the correct image is served for the user's device.

Default values: [640, 750, 828, 1080, 1200, 1920, 2048, 3840]

Limit: Maximum 25 values

Related configuration - imageSizes: For images with sizes prop that are less than full screen width:

module.exports = {
  images: {
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  }
}

The imageSizes values should all be smaller than the smallest size in deviceSizes. Both arrays are concatenated to form the complete set of sizes used to generate image srcsets.

Sources:

99% confidence

server_actions

8 questions
A

Create a Server Action by adding the "use server" directive to an async function. There are two approaches:

Option 1: Inline in a Server Component

// app/page.tsx (Server Component)
export default function Page() {
  async function createPost(formData: FormData) {
    'use server'
    // This runs on the server
    const title = formData.get('title')
    // Process data, update database, etc.
  }

  return (
    <form action={createPost}>
      <input type="text" name="title" />
      <button type="submit">Submit</button>
    </form>
  )
}

Option 2: In a separate file (module-level)

// app/actions.ts
'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title')
  // Server-side logic here
}

Then import and use it:

// app/page.tsx or Client Component
import { createPost } from './actions'

export default function Page() {
  return <form action={createPost}>...</form>
}

Usage patterns:

  • Forms: Pass to the action prop (works in both Server and Client Components)
  • Event handlers: Call in Client Components with onClick, onChange, etc. (requires 'use client')
  • useEffect: Invoke inside Client Component effects

The 'use server' directive must be at the top of the function body or at the top of the file (before any imports) for module-level marking.

Sources:

99% confidence
A

Yes. You can call Server Actions from Client Components in Next.js, but you cannot define them in Client Components.

How it works:

  1. Define the Server Action in a separate file with "use server" directive at the top
  2. Import that Server Action into your Client Component
  3. Call it in forms, event handlers, or pass it as props

Example:

// app/actions.ts
"use server"

export async function createPost(formData: FormData) {
  // Server-side logic here
}
// app/components/form.tsx
"use client"

import { createPost } from '@/app/actions'

export function CreatePostForm() {
  return (
    <form action={createPost}>
      <button type="submit">Create Post</button>
    </form>
  )
}

You can also call Server Actions in event handlers or use .bind() to pass additional arguments.

Sources:

99% confidence
A

Define a Zod schema matching your form fields, then use safeParse() to validate the FormData in your Server Action:

import { z } from 'zod'

// Define validation schema
const schema = z.object({
  email: z.string({
    invalid_type_error: 'Invalid Email',
  }),
  name: z.string().min(1, 'Name is required'),
})

// Server Action
export async function createUser(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
    name: formData.get('name'),
  })

  // Check validation result
  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
    }
  }

  // Access validated data
  const { email, name } = validatedFields.data
  
  // Mutate data...
}

Key steps:

  1. Import Zod and define a schema with z.object()
  2. Extract form fields from FormData using .get()
  3. Call schema.safeParse() with the form data
  4. Check validatedFields.success - if false, return flattened errors
  5. If valid, access typed data via validatedFields.data

Use .flatten().fieldErrors to get a field-level error object suitable for displaying validation messages next to form inputs.

Sources:

99% confidence
A

Yes. Server Actions in Next.js have built-in CSRF protection through multiple mechanisms:

1. POST-only method restriction - Server Actions only accept POST requests, which provides baseline CSRF protection in modern browsers (especially with SameSite cookies being default).

2. Origin/Host header validation - Next.js compares the Origin header against the Host header (or X-Forwarded-Host). If they don't match, the request is rejected. This means Server Actions can only be invoked from the same host as the page hosting them.

3. Configuration for complex setups - For applications using reverse proxies or multi-layered architectures, you can configure allowed origins:

module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
    },
  },
}

Important: Server Actions do NOT use CSRF tokens. The protection relies on HTTP method restriction, SameSite cookies, and origin validation. You should still treat Server Actions with the same security rigor as public-facing API endpoints.

Sources:

99% confidence
A

POST

Server Actions in Next.js exclusively use the POST HTTP method. Only POST requests can invoke Server Actions - no other HTTP method is permitted.

Behind the scenes, when you define a Server Action (using "use server"), Next.js creates an endpoint that accepts only POST requests. This design choice provides built-in security benefits, as POST requests prevent CSRF vulnerabilities in modern browsers (especially with SameSite cookies as the default).

// Server Action example
"use server"

export async function createUser(formData: FormData) {
  // This will only be invoked via POST
  const name = formData.get('name')
  // ... mutation logic
}

Sources:

99% confidence
A

Use JavaScript's .bind() method to pass additional arguments to a Server Action beyond the form data.

How it works

Call .bind(null, argument) on your Server Action to create a new function with the argument pre-filled. The first parameter to .bind() is the this context (use null for Server Actions), and subsequent parameters become the bound arguments.

Example

Client Component:

'use client'

import { updateUser } from './actions'

export function UserProfile({ userId }: { userId: string }) {
  const updateUserWithId = updateUser.bind(null, userId)

  return (
    <form action={updateUserWithId}>
      <input type="text" name="name" />
      <button type="submit">Update User Name</button>
    </form>
  )
}

Server Action:

'use server'

export async function updateUser(userId: string, formData: FormData) {
  // userId is the bound argument
  // formData contains the form fields
}

The bound argument (userId) becomes the first parameter to the Server Action, followed by the formData.

Key Points

  • Works in both Server and Client Components
  • Supports progressive enhancement
  • Bound values are encoded (unlike hidden input fields which are visible in rendered HTML)
  • You can bind multiple arguments: action.bind(null, arg1, arg2)

Sources:

99% confidence
A

Yes. Server Actions work with progressive enhancement when passed directly to a <form> element in Server Components. The form will function and submit data even if JavaScript is disabled or hasn't loaded yet.

Key points:

  • Server Components: Full progressive enhancement by default. Forms are interactive before hydration occurs and work without JavaScript.

  • Client Components: Forms queue submissions if JavaScript isn't loaded, then process them after hydration. The browser does not refresh on form submission after hydration.

Example:

// app/actions.ts
'use server'
 
export async function createUser(formData: FormData) {
  const name = formData.get('name')
  // Process data...
}

// app/page.tsx (Server Component)
import { createUser } from './actions'
 
export default function Page() {
  return (
    <form action={createUser}>
      <input name="name" type="text" />
      <button type="submit">Create</button>
    </form>
  )
}

This form will submit and execute the Server Action even with JavaScript disabled.

Sources:

99% confidence
A

Use revalidatePath() or revalidateTag() inside your Server Action to invalidate the cache after a mutation.

revalidatePath

Invalidates cached data for a specific route:

'use server'
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  // Perform mutation (e.g., database update)
  await db.posts.create({ ... })
  
  // Invalidate cache for the /posts route
  revalidatePath('/posts')
}

Parameters:

  • path (string): Route pattern like /product/[slug] or specific URL like /product/123
  • type (optional): 'page' or 'layout' - required for dynamic segments

revalidateTag

Invalidates cached data by tag across all routes that use it:

'use server'
import { revalidateTag } from 'next/cache'

export async function updatePost(id: string) {
  // Perform mutation
  await db.posts.update({ id, ... })
  
  // Invalidate all cache entries tagged with 'posts'
  revalidateTag('posts', 'max')
}

Parameters:

  • tag (string): Cache tag identifier (max 256 chars)
  • profile: Use "max" for stale-while-revalidate behavior (recommended)

Note: To use tags, you must first tag your data fetches:

fetch('https://api.example.com/posts', { 
  next: { tags: ['posts'] } 
})

Which to Use?

  • revalidatePath: When you know the specific page(s) that need updating
  • revalidateTag: When the same data appears across multiple pages

Sources:

99% confidence

font_optimization

7 questions
A

Import the font from next/font/google, configure it with options, and apply its className to your HTML elements.

For variable fonts (recommended):

import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

For non-variable fonts (must specify weight):

import { Roboto } from 'next/font/google'

const roboto = Roboto({
  weight: '400',
  subsets: ['latin'],
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={roboto.className}>
      <body>{children}</body>
    </html>
  )
}

Multiple weights:

const roboto = Roboto({
  weight: ['400', '700'],
  subsets: ['latin'],
})

The font is automatically self-hosted as a static asset—no external requests to Google Fonts occur at runtime.

Sources:

99% confidence
A

Import localFont from next/font/local and specify the path to your local font file using the src parameter.

Basic usage (single font file):

import localFont from 'next/font/local'

const myFont = localFont({
  src: './my-font.woff2',
  display: 'swap',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={myFont.className}>
      <body>{children}</body>
    </html>
  )
}

Multiple weights/styles (font family):

import localFont from 'next/font/local'

const roboto = localFont({
  src: [
    {
      path: './Roboto-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: './Roboto-Italic.woff2',
      weight: '400',
      style: 'italic',
    },
    {
      path: './Roboto-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
    {
      path: './Roboto-BoldItalic.woff2',
      weight: '700',
      style: 'italic',
    },
  ],
})

Font file location:

  • Store fonts in the public folder, OR
  • Co-locate them inside the app folder

The src path is relative to where the loader function is called.

Key options:

  • display: Controls font loading behavior ('swap' is default)
  • variable: Creates a CSS variable name for the font
  • preload: Whether to preload the font (default: true)
  • adjustFontFallback: Reduces layout shift (default: 'Arial')

Sources:

99% confidence
A

Yes. next/font automatically self-hosts Google Fonts.

When you use next/font/google:

  1. Font files are downloaded at build time
  2. Fonts are stored as static assets in your deployment
  3. Fonts are served from the same domain as your application
  4. No requests are sent to Google by the browser

This means zero external network calls to Google's servers, improving privacy, performance, and preventing layout shift.

Example:

import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

The font files are automatically downloaded, subset, and bundled during build—no additional configuration needed.

Sources:

99% confidence
A

No. External network requests to Google are not made when using next/font/google.

How It Works

  • Build time: Next.js downloads the font files from Google Fonts and includes them as static assets
  • Runtime: Fonts are self-hosted and served from your own domain
  • Browser behavior: No requests are sent to Google by users' browsers

Benefits

This self-hosting approach provides:

  1. Privacy: Google doesn't receive requests from your users
  2. Performance: No external network latency for font loading
  3. Reliability: Fonts load even if Google's servers are unreachable

The fonts are downloaded once during your build process and then served as static assets alongside your application code.

Sources:

99% confidence
A

Use the variable option to define a CSS variable name, then apply it via the .variable property in className:

import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.variable}>
      <body>{children}</body>
    </html>
  )
}

Then reference the CSS variable in your CSS:

.text {
  font-family: var(--font-inter);
}

Or in Tailwind config:

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['var(--font-inter)'],
      },
    },
  },
}

For multiple fonts, combine the .variable properties:

const inter = Inter({ variable: '--font-inter' })
const robotoMono = Roboto_Mono({ variable: '--font-roboto-mono' })

<html className={`${inter.variable} ${robotoMono.variable}`}>

Sources:

99% confidence
A

To load multiple font weights with next/font/local, set the src parameter to an array of objects, where each object specifies path, weight, and style:

import localFont from 'next/font/local'

const myFont = localFont({
  src: [
    {
      path: './fonts/MyFont-Regular.woff2',
      weight: '400',
      style: 'normal',
    },
    {
      path: './fonts/MyFont-Bold.woff2',
      weight: '700',
      style: 'normal',
    },
  ],
})

Each object in the array defines a specific font file variant. The browser will automatically select the correct file based on the font-weight and font-style CSS properties applied to your elements.

You can include as many weight/style combinations as needed (100-900 for weights, normal/italic for styles).

Sources:

99% confidence
A

Variable fonts provide better performance and flexibility when using next/font.

Specific Benefits:

Single file for all variations: Variable fonts contain multiple weights and styles in one file, reducing the number of font files that need to be downloaded.

Simplified configuration: With variable fonts, you don't need to specify individual weight values. You can use the string 'variable' for the weight parameter, whereas non-variable fonts require you to specify exact weights (like ['400', '700']).

Optimal performance: Combining Next.js's automatic font optimization (self-hosting, no layout shift, no external network requests) with variable fonts' single-file approach minimizes the resources clients need to download.

Design flexibility: Variable fonts support the full range of weights and styles without requiring separate font files for each variation.

Example configuration:

import { Inter } from 'next/font/google'

// Variable font - no weight array needed
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
})

Sources:

99% confidence

environment_variables

7 questions
A

Create a .env.local file in your project root with key-value pairs:

DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword

Syntax rules:

  • One variable per line
  • Format: KEY=value
  • No quotes needed (unless part of the actual value)
  • No spaces around the = sign

Server vs Browser access:

  • Server-only (default): Regular variables are only available in Node.js environment

    DATABASE_URL=postgresql://localhost:5432
    API_SECRET=abc123
    
  • Browser-accessible: Prefix with NEXT_PUBLIC_ to expose to the browser

    NEXT_PUBLIC_API_URL=https://api.example.com
    NEXT_PUBLIC_ANALYTICS_ID=GA-12345
    

Important notes:

  • .env.local is loaded in development and production (but NOT in test environment)
  • Always add .env.local to .gitignore (done by default with create-next-app)
  • NEXT_PUBLIC_ variables are inlined into the JavaScript bundle at build time

Sources:

99% confidence
A

Next.js loads environment variables from different .env* files based on NODE_ENV and file priority.

.env.local:

  • Loaded in all environments (development, production) except test
  • Used for local overrides and secrets that shouldn't be committed to git
  • Higher priority than environment-specific files like .env.production
  • Should be in .gitignore
  • Skipped during NODE_ENV=test to ensure consistent test results

.env.production:

  • Loaded only when NODE_ENV=production
  • Used for production-specific defaults
  • Can be committed to version control (should not contain secrets)
  • Lower priority than .env.production.local and .env.local

Loading Priority (highest to lowest):

  1. process.env
  2. .env.$(NODE_ENV).local (e.g., .env.production.local)
  3. .env.local (not loaded when NODE_ENV=test)
  4. .env.$(NODE_ENV) (e.g., .env.production)
  5. .env

Example:

  • Running next build sets NODE_ENV=production
  • Variables are loaded from: .env.production.local.env.local.env.production.env
  • Running next dev sets NODE_ENV=development
  • Variables are loaded from: .env.development.local.env.local.env.development.env

Sources:

99% confidence
A

Prefix environment variables with NEXT_PUBLIC_ to expose them to the browser.

How it works

  1. Define the variable in your .env file:
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_ANALYTICS_ID=abc123
  1. Access it in your code (client or server):
// Works in browser and server
const apiUrl = process.env.NEXT_PUBLIC_API_URL
  1. Next.js inlines these values at build time, replacing all references to process.env.NEXT_PUBLIC_* with the actual hardcoded value in the JavaScript bundle sent to the browser.

Critical constraints

  • Build-time only: Values are frozen when you run next build. Runtime changes to these variables have no effect.
  • Not truly secret: Since they're embedded in client-side JavaScript, anyone can inspect them. Never use for API keys, secrets, or credentials.
  • Server-only variables: Variables without NEXT_PUBLIC_ prefix remain server-only and are not accessible in the browser.

Example transformation

Your code:

setupAnalytics(process.env.NEXT_PUBLIC_ANALYTICS_ID)

After build:

setupAnalytics('abc123')

Sources:

99% confidence
A

The NEXT_PUBLIC_ prefix makes environment variables accessible in the browser by inlining their values into the JavaScript bundle at build time.

How It Works

Server-only by default: Environment variables without the NEXT_PUBLIC_ prefix are only available in the Node.js environment (server-side).

Browser exposure: Adding the NEXT_PUBLIC_ prefix tells Next.js to make the variable available in browser JavaScript.

Build-time inlining: Next.js replaces all references to process.env.NEXT_PUBLIC_VARIABLE_NAME with the actual hard-coded value during the next build process. The value gets baked into the client-side JavaScript bundle.

Example

// .env.local
NEXT_PUBLIC_ANALYTICS_ID=abc123
DATABASE_URL=secret

// In your component
console.log(process.env.NEXT_PUBLIC_ANALYTICS_ID) // "abc123" (works in browser)
console.log(process.env.DATABASE_URL) // undefined (browser), "secret" (server)

Critical Limitations

  1. Values are frozen at build time - After building, the app won't respond to changes in NEXT_PUBLIC_ variables
  2. Dynamic lookups don't work - process.env[variableName] won't be inlined, only direct references like process.env.NEXT_PUBLIC_API_KEY
  3. Same build = same values - Deploying one Docker image to multiple environments will have identical NEXT_PUBLIC_ values from the original build

Runtime Alternative

For values that must change per environment after deployment, you must set up your own API to provide them to the client.

Sources:

99% confidence
A

Yes. NEXT_PUBLIC_ environment variables are embedded directly into the JavaScript bundle at build time through inline replacement.

When you run next build, Next.js replaces all references to process.env.NEXT_PUBLIC_* with their literal values. For example:

// Before build
setupAnalyticsService(process.env.NEXT_PUBLIC_ANALYTICS_ID)

// After build (in the bundle)
setupAnalyticsService('abcdefghijk')

Critical implications:

  • Values are frozen at build time
  • Changing environment variables after build has no effect
  • All NEXT_PUBLIC_ variables are visible in client-side JavaScript (viewable in browser)
  • Cannot be used for runtime environment values (use API routes for that)

Sources:

99% confidence
A

No, Next.js does not load .env.local in the test environment.

When NODE_ENV is set to test, Next.js intentionally skips .env.local to ensure tests produce the same results for everyone. This way every test execution uses the same env defaults across different executions by ignoring your .env.local (which is intended to override the default set).

For test environments, use:

  • .env.test - Should be committed to your repository for shared test defaults
  • .env.test.local - Not loaded by default, and should be in .gitignore

Environment files loaded in test environment (in order):

  1. .env.test
  2. .env

Environment files NOT loaded in test:

  • .env.local
  • .env.development
  • .env.production

Sources:

99% confidence
A

Next.js environment variables are looked up in the following order, stopping once the variable is found:

  1. process.env - System environment variables (highest priority)
  2. .env.$(NODE_ENV).local - Environment-specific local overrides (e.g., .env.development.local, .env.production.local)
  3. .env.local - Local overrides for all environments (NOT checked when NODE_ENV is test)
  4. .env.$(NODE_ENV) - Environment-specific defaults (e.g., .env.development, .env.production)
  5. .env - Base defaults (lowest priority)

Key behaviors:

  • Variables defined higher in the list override those lower
  • .env.local is skipped entirely when NODE_ENV=test to ensure consistent test behavior
  • Valid NODE_ENV values: production, development, test
  • .env.* files should be placed in the project root, even when using a /src directory

Example: If DATABASE_URL is defined in both .env.development.local and .env, and you run NODE_ENV=development, the value from .env.development.local will be used.

Sources:

99% confidence

rendering_strategies

7 questions
A

Use SSG when you can pre-render a page ahead of a user's request. This is the key decision criterion.

Best for:

  • Marketing pages
  • Blog posts and articles
  • E-commerce product listings
  • Documentation
  • Help pages
  • Any content that doesn't change frequently

Why use it:

  • Page HTML is generated at build time (next build)
  • Built once, served by CDN on every request
  • Maximum performance and SEO benefits
  • Both HTML and JSON files can be cached

When NOT to use SSG:

  • Content changes on every request
  • Frequently updated data
  • User-specific or personalized content

Alternatives for dynamic content:

  • Server-Side Rendering (SSR) - for always fresh data
  • Client-side fetching - for portions of the page
  • Incremental Static Regeneration (ISR) - for periodic updates

Next.js recommends using Static Generation whenever possible because it's faster than server-rendering on each request.

Sources:

99% confidence
A

Based on the official Next.js documentation, here's when to use SSR:

Use SSR When You Need:

1. Frequently Updated Data

  • Your page needs to pre-render data that changes on each request (fetched from an external API)
  • Data is personalized or request-specific and cannot be pre-rendered at build time

2. Request-Time Information

  • You need access to request headers, cookies, or URL parameters to determine what to render
  • Content varies based on the incoming request

Next.js Rendering Context:

Pages Router (using getServerSideProps):

  • Use SSR for pages with data that must be fresh on every request
  • Page HTML is generated server-side for each incoming request

App Router (using Server Components by default):

  • Server Components are the default - use them for:

    • Fetching data from databases/APIs close to the source
    • Keeping sensitive data (API keys, tokens) on the server
    • Reducing client-side JavaScript bundle size
  • Switch to Client Components only when you need:

    • Interactivity (event handlers, state)
    • Browser APIs (localStorage, window, etc.)
    • React hooks (useEffect, useState)

Key Decision Rule:

Ask: "Can this page be pre-rendered at build time?"

  • NO → Use SSR (data changes per request)
  • YES → Use Static Generation instead (better performance)

Sources:

99% confidence
A

Use ISR (Incremental Static Regeneration) in Next.js when you have static content that needs periodic updates without rebuilding your entire site.

When to Use ISR:

1. Content-heavy sites at scale

  • Blogs with hundreds or thousands of posts
  • E-commerce sites with large product catalogs
  • Documentation sites
  • News sites with frequent but not real-time updates

2. Content that updates periodically

  • Data that changes hourly, daily, or on a schedule
  • Content management systems where editors publish updates
  • Product information that refreshes regularly

3. High-traffic applications needing performance

  • ISR serves prerendered static pages for most requests, reducing server load
  • Pages are cached and served statically until revalidation time expires

4. On-demand content updates

  • Use revalidatePath() or revalidateTag() to trigger updates via webhooks or API routes
  • Example: CMS webhook triggers revalidation when content is published

When NOT to Use ISR:

1. Static exports - ISR requires a Node.js server and is explicitly unsupported for static exports

2. Real-time data requirements - If you need instant updates (live scores, stock tickers, chat), use dynamic rendering instead

3. Non-Node.js runtimes - ISR only works with the Node.js runtime, not Edge Runtime

Example Implementation:

// App Router
export const revalidate = 3600; // Revalidate every hour

export default async function Page() {
  const data = await fetch('https://api.example.com/data');
  return <div>{/* content */}</div>;
}
// Pages Router
export async function getStaticProps() {
  return {
    props: { data },
    revalidate: 3600, // Revalidate every hour
  };
}

Sources:

99% confidence
A

SSG (Static Site Generation) generates HTML at build time. Pages are pre-rendered once during the build process and remain static until you rebuild the entire site. Content cannot be updated without triggering a new build.

ISR (Incremental Static Regeneration) extends SSG by allowing pages to be regenerated in the background after the initial build. You add a revalidate time (in seconds) to getStaticProps:

export async function getStaticProps() {
  const res = await fetch('https://...')
  const data = await res.json()

  return {
    props: { data },
    revalidate: 60 // Regenerate page after 60 seconds
  }
}

How ISR works:

  1. Page is pre-rendered at build time (like SSG)
  2. Cached page serves instantly for all visitors
  3. After the revalidate time expires, the next request still receives the cached page but triggers a background regeneration
  4. Once regeneration completes, the cache updates with the new page

Key difference: SSG requires a full rebuild to update content. ISR updates individual pages automatically based on the revalidate interval, or on-demand via API routes (available since Next.js 12.2.0).

Sources:

99% confidence
A

Yes. Next.js allows you to use different rendering strategies for different pages within the same application.

This is explicitly stated in the official documentation: "One of the major strengths of Next.js is that each one of the above rendering methods can be done on a per-page basis."

How it works:

Each page in your Next.js application can independently choose from:

  • Static Site Generation (SSG) - Use getStaticProps to pre-render at build time
  • Server-Side Rendering (SSR) - Use getServerSideProps to render on each request
  • Incremental Static Regeneration (ISR) - Use revalidate option with getStaticProps to update static pages periodically
  • Client-Side Rendering (CSR) - Fetch data on the client using React hooks

Example mix:

// pages/blog/[slug].tsx - Static Generation
export async function getStaticProps() { ... }

// pages/dashboard.tsx - Server-Side Rendering  
export async function getServerSideProps() { ... }

// pages/profile.tsx - Client-Side Rendering
// (no data fetching functions, uses useEffect)

In Next.js App Router (13+), the framework automatically chooses the best rendering strategy per route based on features used, or you can mix static/cached/dynamic content within a single route using the use cache directive and <Suspense> boundaries.

Sources:

99% confidence
A

When the ISR revalidation time expires in Next.js, the page follows a stale-while-revalidate pattern:

  1. First request after expiration: Returns the cached (now stale) page immediately to the user

  2. Background regeneration: Next.js triggers a regeneration of the page in the background

  3. Cache invalidation: Once the new page generates successfully, Next.js invalidates the old cache and replaces it with the updated page

  4. Subsequent requests: Receive the freshly regenerated page

Key behavior: Users never wait for regeneration. The stale page is served instantly while Next.js updates the cache behind the scenes. If background regeneration fails, the old page remains unchanged.

Example with revalidate: 60:

  • Minutes 0-1: All visitors see the same cached version (instantaneous)
  • Minute 1+: Next request still gets the cached page but triggers background rebuild
  • After rebuild completes: New requests get the updated page

Sources:

99% confidence
A

Yes. ISR uses a stale-while-revalidate strategy.

When the revalidation period expires, the next visitor receives the cached (stale) page immediately, while Next.js regenerates a fresh version in the background. Once regeneration completes, subsequent requests receive the updated page.

This ensures:

  • Zero downtime during regeneration
  • Fast response times (serving from cache)
  • The stale page is only replaced after the new version is successfully built

The Cache-Control header is set to: s-maxage=<revalidate>, stale-while-revalidate

Sources:

99% confidence

common_errors

6 questions
A

A hydration error in Next.js occurs when the HTML generated on the server doesn't match the HTML React expects to render on the client during hydration—the process where React attaches event handlers and interactivity to the server-rendered HTML.

What Causes Hydration Errors

Common causes:

  1. Invalid HTML nesting - Using incorrect tag hierarchies:

    • <p> inside <p>
    • <div> inside <p>
    • <ul> or <ol> inside <p>
    • Interactive elements nested: <a> inside <a>, <button> inside <button>
  2. Browser-only APIs in render logic:

    • Using typeof window !== 'undefined' checks
    • Accessing window or localStorage during render
    • Date/time APIs like new Date() that differ between server and client
  3. External modifications:

    • Browser extensions modifying the HTML
    • CDN/Edge configurations (e.g., Cloudflare Auto Minify) altering HTML
    • Incorrectly configured CSS-in-JS libraries

Example of problematic code:

// ❌ Causes hydration error - different on server vs client
function Component() {
  return <div>{Math.random()}</div>
}

Fix using useEffect:

// ✅ Prevents hydration error
function Component() {
  const [value, setValue] = useState(null)
  
  useEffect(() => {
    setValue(Math.random())
  }, [])
  
  return <div>{value}</div>
}

Fix using dynamic import:

// ✅ Disable SSR for specific component
import dynamic from 'next/dynamic'

const NoSSR = dynamic(() => import('./component'), { ssr: false })

Sources:

99% confidence
A

Perfect! I found the authoritative answer with exact code examples from the official React documentation.


Use suppressHydrationWarning={true} on elements containing Date() to silence the hydration mismatch warning:

export default function App() {
  return (
    <h1 suppressHydrationWarning={true}>
      Current Date: {new Date().toLocaleDateString()}
    </h1>
  );
}

Important: This only works one level deep and is intended as an escape hatch.

Alternative: Two-Pass Rendering with useEffect

For more control, render nothing on the server and populate the date only on the client using state and useEffect:

import { useState, useEffect } from "react";

export default function App() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  return <h1>{isClient ? new Date().toLocaleDateString() : ''}</h1>;
}

Or with the actual date value:

'use client'
import { useState, useEffect } from 'react'

export default function TimeDisplay() {
  const [time, setTime] = useState('')
  
  useEffect(() => {
    setTime(new Date().toLocaleString())
  }, [])
  
  return <div>{time || 'Loading...'}</div>
}

This pattern ensures the server and client render the same initial content (empty string), then updates to show the date after hydration completes.

Sources:

99% confidence
A

To fix hydration errors caused by browser extensions in Next.js, use the suppressHydrationWarning prop on elements that extensions are likely to modify (typically <html>, <head>, or <body>):

<html suppressHydrationWarning>
  <body suppressHydrationWarning>
    {children}
  </body>
</html>

How it works: Browser extensions often inject scripts, styles, or modify the DOM before React hydrates, causing mismatches between server-rendered HTML and client expectations. The suppressHydrationWarning prop tells React to skip mismatch warnings for that element (one level deep only).

React 19 improvement: If you upgrade to React 19 (Next.js 15 uses React 19), this problem is largely solved automatically. React 19 now skips over unexpected tags inserted by browser extensions in <head> and <body>, avoiding mismatch errors entirely.

Alternative approaches:

  1. Wrap extension-prone content in client components with useEffect to render only on client side
  2. Use dynamic imports with ssr: false for components affected by extensions
  3. Upgrade to Next.js 15 (which uses React 19) for automatic handling

Important: Only use suppressHydrationWarning as an escape hatch for content you know will differ. Don't apply it broadly across your application.

Sources:

99% confidence
A

No. suppressHydrationWarning does not fix hydration errors—it only silences the warning.

What It Actually Does

suppressHydrationWarning={true} prevents React from warning you about content mismatches between server and client, but the mismatch still exists. React will not attempt to patch the mismatched content.

Usage

<time dateTime="2016-10-25" suppressHydrationWarning={true}>
  {new Date().toLocaleDateString()}
</time>

Critical Limitations

  • Works one level deep only - only suppresses warnings on the direct element, not children
  • Intended as an escape hatch - not a solution for general hydration errors
  • Don't overuse it - the official docs explicitly warn against overuse

When It's Appropriate

Use it only for unavoidable differences between server and client, such as:

  • Timestamps that will always differ
  • Time-sensitive data that can't be synchronized

How to Actually Fix Hydration Errors

Instead of suppressing warnings, fix the root cause:

  1. Ensure server and client render the same initial content
  2. Use useEffect for client-only content
  3. Use dynamic imports with ssr: false for components that can't be server-rendered
  4. Avoid browser-only APIs during initial render

Sources:

99% confidence
A

Use next/dynamic with the ssr: false option to disable server-side rendering for specific components:

'use client'

import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() => import('../components/YourComponent'), {
  ssr: false,
})

export default function Page() {
  return (
    <div>
      <DynamicComponent />
    </div>
  )
}

This prevents the component from rendering on the server, loading it only on the client side. This is useful when:

  • Components rely on browser APIs like window or document
  • You need to avoid hydration mismatches between server and client
  • Third-party libraries require browser-only features

Important: When using suspense: true with React 18+, the ssr: false option is ignored because React will always try to resolve the Suspense boundary on the server.

Alternative for suppressHydrationWarning: For intentional content differences, add suppressHydrationWarning={true} to the element (works one level deep only):

<time datetime="2016-10-25" suppressHydrationWarning />

Sources:

99% confidence
A

Use useEffect with a state variable to ensure the server and client render identical initial HTML, then conditionally render client-specific content after hydration completes.

The Pattern

function MyComponent() {
  const [didMount, setDidMount] = useState(false);
  
  useEffect(() => {
    setDidMount(true);
  }, []);
  
  if (didMount) {
    // ... return client-only JSX ...
  } else {
    // ... return initial JSX ...
  }
}

Why This Works

  • useEffect only runs on the client, never during server rendering
  • The didMount state starts as false on both server and client initial render
  • This ensures identical HTML output during hydration (preventing mismatches)
  • After hydration completes, the Effect runs and sets didMount to true
  • The component re-renders with client-specific content

Common Use Cases

// Browser APIs (window, localStorage, etc.)
const [windowWidth, setWindowWidth] = useState(0);

useEffect(() => {
  setWindowWidth(window.innerWidth);
}, []);

// Conditional rendering based on client state
if (windowWidth === 0) {
  return <div>Loading...</div>; // Server render
}
return <div>Width: {windowWidth}px</div>; // Client render

Important Considerations

  • Use this pattern sparingly - users on slow connections will see the initial content for extended periods
  • Avoid jarring visual changes between server and client renders
  • Don't use typeof window !== 'undefined' checks in render logic (causes hydration errors)

Sources:

99% confidence

database_integration

5 questions
A

Based on the official Prisma documentation from the web search results:

For serverless deployments (including Next.js on Vercel), set connection_limit=1 as the starting point.

In serverless environments, each function invocation creates a short-lived Node.js process with its own Prisma Client instance and connection pool. During traffic spikes, many concurrent functions with higher connection limits can quickly exhaust your database's maximum connections.

Configuration:

DATABASE_URL="postgresql://user:password@host:5432/database?connection_limit=1"

After setting connection_limit=1, you can optimize based on your actual performance needs.

Exception: If you're using an external connection pooler (like PgBouncer), use the default pool size formula (num_physical_cpus * 2 + 1) as your starting point instead, since the pooler prevents connection exhaustion.

Additional Next.js Best Practice: Use a singleton pattern for PrismaClient in development to prevent multiple instances from creating separate connection pools.

Sources:

99% confidence
A

Based on the official Prisma documentation, create a singleton pattern by storing the PrismaClient instance on the globalThis object to prevent multiple instances during Next.js hot reloading in development:

import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined
}

export const prisma = globalForPrisma.prisma ?? new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

Why this works:

  • In development, Next.js hot-reloading can cause the module to be reinitialized multiple times
  • Each new PrismaClient() instance creates its own connection pool
  • Storing the instance on globalThis prevents creating new instances on hot reload
  • In production, this singleton behavior isn't needed since there's no hot reloading

Usage:
Import from this file throughout your application instead of creating new PrismaClient instances:

import { prisma } from '@/lib/prisma' // or wherever you placed the singleton

// Use prisma in your API routes, Server Components, etc.
const users = await prisma.user.findMany()

Sources:

99% confidence
A

Based on the web search results, I can provide an authoritative answer from Prisma's official documentation:

YES - Use PgBouncer (or external connection pooling) with Prisma in serverless Next.js deployments.

In serverless environments, each function invocation creates new database connections. Without connection pooling, you'll quickly exhaust your database's connection limit. PgBouncer acts as an external connection pooler between Prisma Client and your database, managing a pool of connections that can be shared across serverless function invocations.

Configuration:

Add the pgbouncer=true flag to your PostgreSQL connection URL:

datasource db {
  provider = "postgresql"
  url      = "postgresql://USER:PASSWORD@HOST:PORT/DATABASE?pgbouncer=true"
}

For Next.js on Vercel specifically, use a pooled connection string:

POSTGRES_PRISMA_URL="postgres://user:[email protected]:5432/name?pgbouncer=true&connect_timeout=15"

Additional serverless recommendations:

  • Set connection_limit = 1 in your Prisma Client configuration as a starting point for serverless
  • Use connection pooling services (PgBouncer, Supavisor, or provider-specific poolers)

Sources:

99% confidence
A

Instantiate PrismaClient once as a singleton in a separate module, then import that instance in your API routes.

Create a file (e.g., lib/prisma.ts or db.ts) with this pattern:

import { PrismaClient } from '@prisma/client'

const prismaClientSingleton = () => {
  return new PrismaClient()
}

declare const globalThis: {
  prismaGlobal: ReturnType<typeof prismaClientSingleton>;
} & typeof global;

const prisma = globalThis.prismaGlobal ?? prismaClientSingleton()

export default prisma

if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma

Then import it in your API routes:

import prisma from '@/lib/prisma'

export default async function handler(req, res) {
  const users = await prisma.user.findMany()
  res.json(users)
}

Why this pattern: In development, Next.js hot reloading clears the Node.js cache on each change, which would create a new PrismaClient instance every time. Since each instance maintains its own connection pool, this quickly exhausts database connections. The globalThis approach ensures only one instance exists across hot reloads in development, while staying clean in production.

Sources:

99% confidence
A

Based on the official Prisma documentation search results, I can provide the authoritative answer:

No, you should NOT explicitly call $disconnect() on PrismaClient in Next.js serverless functions.

In serverless environments like Next.js API routes, serverless containers are often reused across multiple invocations. If you disconnect at the end of each function execution, the next invocation must create a new database connection, which adds latency and degrades performance.

Best practice:

  • Instantiate PrismaClient once (typically as a singleton)
  • Do NOT call $disconnect() at the end of each request
  • Let the serverless platform manage the lifecycle

When you SHOULD disconnect:
Only call $disconnect() for infrequent scripts (like scheduled jobs) that run in the context of a long-running application but don't benefit from persistent connections.

Recommended pattern for Next.js:

// lib/prisma.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = global as unknown as { prisma: PrismaClient }

export const prisma = globalForPrisma.prisma || new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

Sources:

99% confidence

nextjs_15_breaking_changes

4 questions
A

In Next.js 15, the cookies() function is now asynchronous. You must await it before accessing cookie methods.

Basic Usage

import { cookies } from 'next/headers'

export default async function Page() {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return '...'
}

Available Methods

After awaiting cookies(), you can use:

  • get(name) - Get a single cookie
  • getAll() - Get all cookies as an array
  • has(name) - Check if a cookie exists (returns boolean)
  • set(name, value, options) - Set a cookie (Server Actions/Route Handlers only)
  • delete(name) - Delete a cookie (Server Actions/Route Handlers only)

Examples

Reading cookies in Server Components:

const cookieStore = await cookies()
const token = cookieStore.get('token')
const allCookies = cookieStore.getAll()
const hasAuth = cookieStore.has('auth')

Setting cookies in Server Actions:

'use server'
import { cookies } from 'next/headers'

export async function createSession(data: string) {
  const cookieStore = await cookies()
  cookieStore.set('session', data, { 
    httpOnly: true, 
    secure: true,
    maxAge: 60 * 60 * 24 * 7 // 1 week
  })
}

Deleting cookies:

'use server'
const cookieStore = await cookies()
cookieStore.delete('session')

Important Notes

  • You can only set or delete cookies in Server Actions or Route Handlers (not in Server Components)
  • Reading cookies in layouts or pages triggers dynamic rendering
  • Cookies cannot be modified after streaming starts

Sources:

99% confidence
A

In Next.js 15, headers() is an asynchronous function that returns a Promise. You must await it.

Syntax

import { headers } from 'next/headers'

export default async function Page() {
  const headersList = await headers()
  const userAgent = headersList.get('user-agent')
}

Key Requirements

  • Must use await: headers() returns a Promise, so you must await it
  • Server Components only: Works only in async Server Components
  • Returns Web Headers object: Standard read-only Headers API

Example: Authorization Header

import { headers } from 'next/headers'

export default async function Page() {
  const authorization = (await headers()).get('authorization')
  const res = await fetch('https://api.example.com/data', {
    headers: { authorization }
  })
  const user = await res.json()
  return <h1>{user.name}</h1>
}

Available Methods

After awaiting, you can use:

  • .get(name) - retrieve header value
  • .has(name) - check if header exists
  • .entries(), .keys(), .values() - iterate headers
  • .forEach() - loop through headers

Sources:

99% confidence
A
npx @next/codemod@latest next-async-request-api .

This codemod transforms previously synchronous Dynamic APIs in Next.js 15 that now return promises:

  • cookies(), headers(), and draftMode() from next/headers
  • params and searchParams in pages, layouts, and route handlers

What it does:

  • Adds await to these API calls (e.g., cookies()await cookies())
  • Wraps with React.use() where appropriate
  • Leaves @next-codemod-error comments where manual intervention is needed

Example transformation:

// Before
const cookieStore = cookies()
const headersList = headers()

// After
const cookieStore = await cookies()
const headersList = await headers()

Run the command in your project root (the . specifies the target directory).

Sources:

99% confidence
A

No. Fetch requests are not cached by default in Next.js 15.

This is a breaking change from Next.js 14, where fetch requests using the GET method were cached by default.

What Changed

In Next.js 15:

  • Fetch requests default to no caching (cache: 'no-store' behavior)
  • GET Route Handlers are not cached by default
  • The default cache option is 'no-cache'

How to Enable Caching

To cache fetch requests in Next.js 15, explicitly opt-in:

// Cache a specific fetch request
fetch('https://api.example.com/data', { cache: 'force-cache' })

Or for Route Handlers:

// Force static caching for a route
export const dynamic = 'force-static'

Why the Change

The Next.js team re-evaluated caching heuristics based on user feedback and how caching interacts with features like Partial Prerendering (PPR) and third-party libraries.

Sources:

99% confidence

nextjs_app_router

3 questions
A

Server Components render on the server and send HTML to the client. Client Components render in the browser with JavaScript.

Key Differences:

Where they execute:

  • Server Components: Run only on the server during build or request time
  • Client Components: Run in the browser after initial HTML loads (hydration)

How to create them:

  • Server Components: Default in App Router - no directive needed
  • Client Components: Add "use client" directive at top of file (above imports)

What they can do:

  • Server Components: Direct database/backend access, use secrets securely, no JavaScript sent to client for component logic
  • Client Components: useState, useEffect, event handlers (onClick), browser APIs (localStorage, window), interactivity

When to use:

  • Server Components: Data fetching, static content, SEO-critical content, secure operations
  • Client Components: Interactivity, state management, browser-only features, event listeners

Example:

Server Component (default):

// app/page.tsx
async function Page() {
  const data = await fetch('https://api.example.com/data')
  return <div>{data.title}</div>
}

Client Component:

// components/button.tsx
'use client'

import { useState } from 'react'

export function Button() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

Important: Once you mark a file with "use client", all modules it imports become part of the client bundle.

Sources:

99% confidence
A

Use the 'use client' directive in Next.js 14/15 when your component needs client-side JavaScript features that cannot run on the server.

Specific scenarios requiring 'use client':

  1. React state and lifecycle: useState, useEffect, useReducer, or any React Hook
  2. Event handlers: onClick, onChange, onSubmit, etc.
  3. Browser-only APIs: window, document, localStorage, sessionStorage, navigator.geolocation
  4. Custom hooks that depend on state, effects, or browser APIs
  5. Third-party libraries that depend on browser APIs or React client features

Implementation:

'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}

Key principles:

  • Place 'use client' at the top of the file before any imports
  • You only need it in the entry point file—all imported child components automatically become client components
  • By default, all components in the App Router are Server Components unless marked with 'use client'
  • Use it sparingly to minimize client JavaScript bundle size

What does NOT need 'use client':

  • Components that only render static content
  • Components that fetch data during SSR
  • Components using Server Actions

Sources:

99% confidence
A

No, you cannot use useState in a Server Component in Next.js App Router.

Server Components run only on the server and cannot use React hooks that require interactivity or state (useState, useEffect, event handlers, etc.). These features are only available in Client Components.

To use useState, you must mark your component as a Client Component by adding the "use client" directive at the top of the file:

'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}

What happens if you try: Next.js will throw an error if you attempt to use useState (or other client-only hooks) in a Server Component without the "use client" directive, because these APIs are not available in the server environment.

Common pattern: Keep your page as a Server Component for data fetching, then pass data as props to nested Client Components that handle interactivity.

Sources:

99% confidence

routing_navigation

3 questions
A

307 (Temporary Redirect) is the default HTTP status code returned by redirect() in Next.js.

Exception: When redirect() is called within a Server Action, it returns 303 (See Other) instead.

Why 307?

Next.js uses 307 because it preserves the original HTTP method (e.g., POST remains POST), unlike the legacy 302 status code which browsers often convert from POST to GET.

Example:

import { redirect } from 'next/navigation'

// Returns 307 by default
redirect('/dashboard')

// For permanent redirect, use 308
redirect('/dashboard', 'replace') // or permanent: true

Sources:

99% confidence
A

The key difference is the HTTP status code and caching behavior:

redirect() - Temporary redirect

  • Returns 307 (Temporary Redirect) in most contexts
  • Returns 303 (See Other) in Server Actions
  • Not cached permanently by browsers or search engines
  • Use for temporary URL changes

permanentRedirect() - Permanent redirect

  • Returns 308 (Permanent Redirect) in most contexts
  • Returns 303 (See Other) in Server Actions
  • Cached forever by clients and search engines
  • Use when a resource's canonical URL has permanently changed (e.g., after updating a user's profile URL when they change their username)

Both functions:

  • Can be used in Server Components, Client Components, Route Handlers, and Server Actions
  • Throw an error to stop rendering (use outside try/catch blocks)
  • Don't require a return statement
  • Insert a meta tag for client-side redirection in streaming contexts
// Temporary redirect (307)
redirect('/dashboard')

// Permanent redirect (308)
permanentRedirect('/new-profile-url')

Sources:

99% confidence
A

No, you should not use redirect() inside a try-catch block in Next.js.

redirect() works by throwing a NEXT_REDIRECT error internally to terminate rendering and perform the redirect. If you place it inside a try block, your catch block will catch this error and prevent the redirect from working correctly.

Correct pattern:

'use server'
import { redirect } from 'next/navigation'

export async function createPost(id: string) {
  try {
    // Call database or other operations that might fail
  } catch (error) {
    // Handle errors
  }
  
  redirect(`/post/${id}`) // Call OUTSIDE try-catch
}

The redirect will only execute if the try block succeeds. If an error is thrown and caught, the code will not reach the redirect call.

Sources:

99% confidence

error_handling

2 questions
A

The error.tsx file in Next.js App Router creates a React Error Boundary that catches and handles runtime errors in a route segment and all its nested children.

Primary purpose: Display fallback UI when unexpected errors occur during rendering, preventing the entire application from crashing.

How it works:

  • Automatically wraps a route segment and its nested children in an Error Boundary
  • When an error is thrown within the boundary, the error component replaces the normal UI
  • Must be a Client Component (requires 'use client' directive)

Props received:

type ErrorProps = {
  error: Error & { digest?: string }  // Error instance with optional digest hash
  reset: () => void                    // Function to retry rendering the segment
}

Key characteristics:

  • Errors bubble up to the nearest parent error.tsx boundary, enabling granular error handling at different route levels
  • Does NOT catch errors in the root layout.tsx or template.tsx (use global-error.tsx for those)
  • In production, Server Component error messages are sanitized to prevent leaking sensitive data
  • The error.digest property allows matching client-side errors with server logs

Sources:

99% confidence
A

Yes, error.tsx must be a Client Component in Next.js.

The official Next.js documentation explicitly states: "error.js needs to be a Client Component" and requires the 'use client' directive at the top of the file.

This is mandatory because error boundaries are a React feature that only works client-side - they need access to browser APIs and React hooks like useEffect to catch runtime errors during rendering.

Example:

'use client' // Required
 
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>
  )
}

Without 'use client', the error boundary cannot function properly because it needs client-side capabilities to intercept and display errors.

Sources:

99% confidence

data_fetching

1 question
A

In Next.js 15, fetch requests are not cached by default (changed from previous versions). To enable caching for individual fetch requests, use the cache: 'force-cache' option:

const data = await fetch('https://api.example.com/data', { 
  cache: 'force-cache' 
})

This makes Next.js look for a matching cached request in its Data Cache. If found, it returns the cached data. If not found or stale, it fetches from the remote server and updates the cache.

Alternative: Time-based revalidation

Instead of indefinite caching, you can cache with automatic revalidation after a specified number of seconds:

const data = await fetch('https://api.example.com/data', { 
  next: { revalidate: 3600 } // revalidate after 1 hour
})

All cache options:

  • cache: 'force-cache' - Cache indefinitely (opt-in)
  • cache: 'no-store' - Never cache (default in Next.js 15)
  • next: { revalidate: number } - Cache for specified seconds
  • next: { revalidate: false } - Cache indefinitely (equivalent to force-cache)
  • next: { revalidate: 0 } - Never cache (equivalent to no-store)

Sources:

99% confidence