Fix Next.js 16 AI-Generated Routing Errors in 12 Minutes

Solve common App Router mistakes from AI code generators - dynamic routes, metadata, and async components that actually work.

Problem: AI Tools Generate Broken Next.js 16 Routes

You asked ChatGPT or Claude to write a Next.js route. The code looks perfect, but throws Error: Route "/blog/[slug]" used params.slug without await or metadata doesn't render.

You'll learn:

  • Why AI generates async/await mistakes in App Router
  • How to fix dynamic route parameter access
  • Correct metadata patterns for Next.js 16
  • When to use use client vs server components

Time: 12 min | Level: Intermediate


Why This Happens

AI models trained on Next.js 12-14 code don't know that Next.js 15+ requires:

  • Awaiting route params in dynamic segments
  • Async metadata functions for dynamic data
  • Explicit use client for hooks (even common ones)

Common symptoms:

  • "Route used params without await" errors
  • Metadata shows [object Promise] in browser tab
  • useSearchParams() throws server component error
  • Dynamic imports fail silently

Solution

Step 1: Fix Dynamic Route Parameters

AI generates this (broken in Next.js 16):

// app/blog/[slug]/page.tsx - WRONG
export default function BlogPost({ params }) {
  const { slug } = params; // Error: params is a Promise
  return <h1>{slug}</h1>;
}

Correct version:

// app/blog/[slug]/page.tsx - CORRECT
export default async function BlogPost({ 
  params 
}: { 
  params: Promise<{ slug: string }> 
}) {
  const { slug } = await params; // Await the Promise
  return <h1>{slug}</h1>;
}

Why this works: Next.js 16 changed params to async to enable streaming and partial prerendering. Must await before accessing properties.

Expected: Page renders without errors, slug displays correctly.


Step 2: Fix Metadata Generation

AI generates this (broken):

// WRONG - metadata is synchronous but needs async data
export const metadata = {
  title: params.slug, // params is undefined here
};

export default async function Page({ params }) {
  const { slug } = await params;
  // ...
}

Correct version:

// CORRECT - use generateMetadata function
export async function generateMetadata({ 
  params 
}: { 
  params: Promise<{ slug: string }> 
}) {
  const { slug } = await params;
  
  // Can fetch data here
  const post = await getPost(slug);
  
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      images: [post.coverImage],
    },
  };
}

export default async function Page({ params }) {
  const { slug } = await params;
  const post = await getPost(slug);
  return <article>{post.content}</article>;
}

Why this works: generateMetadata is async and receives the same params Promise as the page component.

If it fails:

  • Metadata still shows Promise: Check you're returning the object, not awaiting the whole function
  • Type errors: Add proper TypeScript types to params

Step 3: Fix Client Component Hook Usage

AI generates this (broken):

// app/search/page.tsx - WRONG
import { useSearchParams } from 'next/navigation';

export default function SearchPage() {
  const searchParams = useSearchParams(); // Error: Server Component
  return <div>{searchParams.get('q')}</div>;
}

Correct version - Option A (extract to client component):

// app/search/page.tsx - Server Component
import SearchResults from './SearchResults';

export default function SearchPage() {
  return <SearchResults />;
}

// app/search/SearchResults.tsx - Client Component
'use client';

import { useSearchParams } from 'next/navigation';

export default function SearchResults() {
  const searchParams = useSearchParams();
  const query = searchParams.get('q');
  
  return <div>Searching for: {query}</div>;
}

Correct version - Option B (use server-side searchParams):

// app/search/page.tsx - Server Component (Next.js 16)
export default async function SearchPage({
  searchParams,
}: {
  searchParams: Promise<{ q?: string }>;
}) {
  const { q } = await searchParams;
  
  // Fetch on server
  const results = await searchDatabase(q);
  
  return (
    <div>
      <h1>Results for: {q}</h1>
      {results.map(r => <div key={r.id}>{r.title}</div>)}
    </div>
  );
}

Why this works:

  • Option A: Hooks require 'use client' directive
  • Option B: Server Components get searchParams as props (also a Promise in Next.js 16)

Expected: No "Server Component cannot use hooks" errors.


Step 4: Fix Async Component Loading

AI generates this (works but suboptimal):

// WORKS but defeats streaming
export default async function Page() {
  const [user, posts, comments] = await Promise.all([
    fetchUser(),
    fetchPosts(),
    fetchComments(),
  ]);
  
  return (
    <>
      <User data={user} />
      <Posts data={posts} />
      <Comments data={comments} />
    </>
  );
}

Better pattern (streaming):

// BETTER - components stream in as ready
import { Suspense } from 'react';

export default function Page() {
  return (
    <>
      <Suspense fallback={<UserSkeleton />}>
        <UserData />
      </Suspense>
      
      <Suspense fallback={<PostsSkeleton />}>
        <PostsData />
      </Suspense>
      
      <Suspense fallback={<CommentsSkeleton />}>
        <CommentsData />
      </Suspense>
    </>
  );
}

// Each component fetches independently
async function UserData() {
  const user = await fetchUser(); // Fast query
  return <User data={user} />;
}

async function PostsData() {
  const posts = await fetchPosts(); // Slower query
  return <Posts data={posts} />;
}

Why this works: Suspense boundaries let fast queries render immediately while slow ones stream in. Better UX than blocking everything.


Verification

Test your fixes:

# Run dev server
npm run dev

# Open browser dev tools, check for:
# 1. No console errors about params/searchParams
# 2. Metadata appears in page source (View → Page Source)
# 3. Dynamic routes load without delays

# Build to catch type errors
npm run build

You should see:

  • Clean console output
  • Proper metadata in <head> tags
  • Fast page loads with streaming content

Common remaining issues:

# If you see "params is not defined"
# Check you spelled it correctly: params not param

# If metadata still broken
# Verify generateMetadata returns object, doesn't have typos

# If hooks still error
# Search for missing 'use client' at top of file

What You Learned

  • Next.js 16 made params and searchParams async - always await them
  • AI models lag behind framework changes by 6-12 months
  • generateMetadata is the only way to set dynamic metadata
  • Server vs Client Components require different data patterns
  • Suspense boundaries enable better streaming UX

Limitations to know:

  • These fixes only apply to App Router (not Pages Router)
  • Middleware still uses synchronous params (different API)
  • Some third-party libraries expect old patterns

Quick Reference: Common AI Mistakes

AI generatesNext.js 16 requiresWhy
params.slug(await params).slugParams is Promise
export const metadata = {...}export async function generateMetadata()Needs async data
useSearchParams() in Server'use client' or searchParams propHooks need client
Promise.all([...]) at top<Suspense> boundariesEnable streaming
params: { slug: string }params: Promise<{ slug: string }>Type accuracy

Quick fix checklist:

  • All params/searchParams are awaited
  • Dynamic metadata uses generateMetadata
  • Hooks are in 'use client' components
  • TypeScript types include Promise<...>
  • No blocking Promise.all unless necessary

Tested on Next.js 16.0.4, React 19, Node.js 22.x Works with Claude Code, GitHub Copilot, ChatGPT o1 generated code