Skip to content

Common Next.js Mistakes

This lesson focuses on mistakes that appear constantly in App Router codebases, why they happen, and how to fix them quickly.

Why These Mistakes Keep Happening

Next.js blends React, a server framework, and a router. Mental models from Create React App, Remix, or the Pages Router all almost fit — which is why subtle mistakes are common. Most fall into five buckets: client/server boundaries, caching, routing conventions, auth, and SEO/metadata.

Recognizing the category of a bug usually points to the fix faster than guessing at random config flags.

// Mistake: Client page fetching public SEO content
"use client";
useEffect(() => {
  fetch("/api/posts").then(...);
}, []);

// Fix: Server Component
export default async function PostsPage() {
  const posts = await getPosts();
  return <PostList posts={posts} />;
}

If content must be crawlable and present on first paint, fetch it on the server — don't hide it behind a client useEffect.

Mistake Categories at a Glance

1. Client/Server boundary mistakes
2. Caching and stale data surprises
3. App Router vs Pages Router API mixups
4. Weak auth on Server Actions
5. Missing or duplicate metadata / routes
  • Boundary mistakes show up as "hooks in Server Components" build errors or bloated bundles.
  • Caching mistakes show up as "I updated the DB but the UI is old".
  • API mixups show up as getServerSideProps / next/router not working in app/.
  • Auth gaps show up as protected UIs that still accept unauthenticated mutations.

Common Mistakes Cheat Sheet

Symptom → likely cause → fix.

Symptom Likely Mistake Fix
Hooks error in a page "use client" missing or inverted Move hooks to a small Client child
Stale page after save No revalidatePath/Tag Revalidate after the mutation
useRouter type/runtime errors Imported from next/router Import from next/navigation
Image domain error Remote host not allow-listed Add images.remotePatterns
Route 404s unexpectedly File not named page.tsx Rename to exact convention

Stale Content After Mutations

Teams forget that fetch results and full route payloads can be cached. After a Server Action writes data, call revalidatePath or revalidateTag for every view that shows that data. If the UI still looks stale, check whether the action actually ran and whether you tagged the original fetch.

"use server";
import { revalidatePath } from "next/cache";

export async function createPost(formData: FormData) {
  await db.post.create({ data: { title: String(formData.get("title")) } });
  revalidatePath("/posts");
}

Accidentally Making a Route Dynamic

Reading cookies() or headers() anywhere in the server tree can force dynamic rendering for the route. If a marketing page suddenly becomes slow, search for those APIs (or cache: "no-store") introduced by a shared layout helper.

  • Move cookie reads out of shared root layouts when possible.
  • Prefer passing user data into dynamic sections instead of dynamizing everything.
  • Confirm with next build symbols (○ vs ƒ).

Common Mistakes

  • This entire lesson is about common mistakes — use the cheat sheet as a quick triage guide.
  • Ignoring build warnings about client/server imports until they become production outages.
  • Copy-pasting Auth middleware examples without securing Server Actions.
  • Assuming preview and production env vars are identical on Vercel.

Key Takeaways

  • Most Next.js bugs cluster around boundaries, caching, router APIs, auth, or metadata.
  • Stale UI after writes almost always means missing revalidation.
  • App Router and Pages Router APIs do not mix — import from the correct package.
  • A tiny Client Component is almost always better than a client entire page.
  • Use next build output to verify rendering strategy after structural changes.

Pro Tip

When debugging "impossible" stale data, reproduce with a hard refresh and then with a soft client navigation separately — different cache layers can make the bug look intermittent depending on how you entered the route.