Skip to content

DOMAIN:FRONTEND:PITFALLS

OWNER: floris (Team Alpha), floor (Team Beta)
UPDATED: 2026-03-24
READ_BEFORE: every frontend task
SEVERITY_CONTEXT: these are real production bugs and performance killers. Not theoretical.


PITFALL:USE_CLIENT_EVERYWHERE

SYMPTOM: entire pages marked "use client". Bundle size 3x larger than necessary. No Server Component benefits.
FREQUENCY: extremely common with LLM-generated code.

ANTI_PATTERN: adding "use client" at the top of a page because one child needs useState.
ANTI_PATTERN: adding "use client" because you saw an import warning about a client-only library.
ANTI_PATTERN: LLM generates "use client" by default because it was trained on pre-App-Router patterns.

// BAD: entire page is client because of one toggle
"use client";
import { useState } from "react";
import { db } from "@/lib/db"; // ERROR: db cannot be used in client component

export default function ProductsPage() {
  const [showFilters, setShowFilters] = useState(false);
  // ... entire page is now client-side
}
// GOOD: isolate the interactive part
// app/products/page.tsx (Server Component — no directive)
import { db } from "@/lib/db";
import { FilterToggle } from "@/components/filter-toggle"; // "use client"

export default async function ProductsPage() {
  const products = await db.query.products.findMany();
  return (
    <div>
      <FilterToggle /> {/* Only this tiny component is client */}
      <ProductGrid products={products} />
    </div>
  );
}

FIX: push "use client" boundary to the smallest leaf component.
CHECK: run next build and review per-route JS sizes. If a page has >100KB client JS, investigate.


PITFALL:PROP_DRILLING_INSTEAD_OF_COMPOSITION

SYMPTOM: props passed through 5+ levels of components. Components in the middle have props they don't use.
FREQUENCY: very common with LLM-generated code.

ANTI_PATTERN: passing user, theme, onAction through Layout → Page → Section → Card → Button.

// BAD: drilling user through components that don't use it
function Layout({ user }) {
  return <Sidebar user={user}><Content user={user} /></Sidebar>;
}
function Content({ user }) {
  return <Card user={user} />;
}
function Card({ user }) {
  return <Avatar user={user} />;
}

FIX_1: use composition — pass the rendered component, not data.

// GOOD: composition avoids drilling
function Layout({ children }) {
  return <Sidebar>{children}</Sidebar>;
}
// Usage: <Layout><Card><Avatar user={user} /></Card></Layout>

FIX_2: use Context for truly global data (auth user, theme).
FIX_3: fetch data where it is needed (Server Components can fetch independently).


PITFALL:HYDRATION_MISMATCHES

SYMPTOM: "Text content does not match server-rendered HTML", UI flicker, broken interactivity.
FREQUENCY: common. See ssr-hydration.md for full details.

TOP_CAUSES:
1. new Date() or Date.now() in server-rendered component → different on server vs client
2. Math.random() for keys or IDs → different on server vs client
3. window, document, localStorage access during render → undefined on server
4. <div> inside <p> or <a> inside <a> → browser auto-corrects HTML
5. Theme class on <html> without suppressHydrationWarning
6. Browser extension injecting attributes (dev-only, ignore)

QUICK_FIX_CHECKLIST:
- [ ] replace Math.random() IDs with useId()
- [ ] move Date formatting to useEffect or pass from server
- [ ] move browser API access to useEffect
- [ ] validate HTML nesting (no block elements inside p/span)
- [ ] add suppressHydrationWarning on html tag for theme providers


PITFALL:BARREL_FILE_IMPORTS

SYMPTOM: massive bundle sizes. "Server Component" errors in client barrel. Build failures.
FREQUENCY: common in monorepos and larger projects.

ANTI_PATTERN: re-exporting everything from components/index.ts.

// components/index.ts (BARREL FILE — DO NOT CREATE)
export { Button } from "./ui/button";
export { Dialog } from "./ui/dialog";
export { ServerOnlyComponent } from "./server-only"; // BREAKS: server component in client barrel
export { HeavyChart } from "./charts/heavy-chart"; // BUNDLES: even if unused

PROBLEMS:
1. importing one component pulls in all exports (breaks tree-shaking)
2. mixing server and client components in one barrel causes build errors
3. Next.js may treat entire barrel as client if any export is client
4. TypeScript errors in unused barrel exports still cause build failures

// BAD: import from barrel
import { Button, Dialog } from "@/components";

// GOOD: import directly from source
import { Button } from "@/components/ui/button";
import { Dialog } from "@/components/ui/dialog";

GE_RULE: no barrel files (index.ts re-exports). Import directly from source file. Always.


PITFALL:USEEFFECT_FOR_DATA_FETCHING

SYMPTOM: loading spinners on every page. Client-side waterfalls. SEO empty pages.
FREQUENCY: extremely common with LLM-generated code (trained on pre-App-Router patterns).

ANTI_PATTERN: client component with useState + useEffect + fetch for initial page data.

// BAD: Pages Router pattern in App Router project
"use client";
import { useState, useEffect } from "react";

export default function ProductsPage() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/products")
      .then((r) => r.json())
      .then(setProducts)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <Spinner />;
  return <ProductGrid products={products} />;
}

FIX: fetch in Server Component. Zero loading spinners. SEO gets real HTML.

// GOOD: Server Component fetches directly
import { db } from "@/lib/db";

export default async function ProductsPage() {
  const products = await db.query.products.findMany();
  return <ProductGrid products={products} />;
}

EXCEPTION: client-side refetching (polling, infinite scroll) — use TanStack Query.


PITFALL:CSS_SPECIFICITY_WARS

SYMPTOM: styles not applying. Adding !important everywhere. Unpredictable cascade.
FREQUENCY: moderate, especially when mixing Tailwind with custom CSS or third-party libraries.

ANTI_PATTERN: using !important to override Tailwind classes.
ANTI_PATTERN: writing custom CSS that competes with Tailwind utility classes.
ANTI_PATTERN: using both Tailwind classes and inline styles on the same element.

// BAD: fighting Tailwind with custom CSS
<div className="p-4" style={{ padding: "8px" }}>  {/* Which padding wins? */}

FIX: use Tailwind exclusively. If custom CSS needed, use @layer to control specificity.
FIX: use cn() (tailwind-merge) to resolve class conflicts.
FIX: for third-party component styling, use data-slot attributes or CSS modules for isolation.

/* If you must write custom CSS, use @layer */
@layer components {
  .custom-card {
    /* These can be overridden by Tailwind utilities */
    border-radius: var(--radius-lg);
    padding: 1.5rem;
  }
}

PITFALL:MISSING_ERROR_BOUNDARIES

SYMPTOM: unhandled error crashes entire page. White screen of death.
FREQUENCY: common. LLMs often generate the happy path without error handling.

ANTI_PATTERN: no error.tsx in route segments.
ANTI_PATTERN: no try/catch in Server Actions.
ANTI_PATTERN: no error handling for API calls in client components.

FIX: every route segment should have error.tsx.
FIX: every Server Action should return errors, not throw them.
FIX: wrap third-party/risky components in custom error boundaries.

// lib/actions/create-project.ts
"use server";

export async function createProject(prevState: ActionState, formData: FormData) {
  try {
    // ... create project
    return { error: null, success: true };
  } catch (error) {
    // GOOD: return error, don't throw
    return { error: "Failed to create project. Please try again.", success: false };
  }
}

PITFALL:OVER_FETCHING_IN_CLIENT_COMPONENTS

SYMPTOM: fetching large datasets on the client. Slow load times. Unnecessary bandwidth.
FREQUENCY: common when converting Pages Router patterns to App Router.

ANTI_PATTERN: fetching all products then filtering on client.

// BAD: fetch 10,000 products, filter on client
"use client";
const allProducts = await fetch("/api/products").then(r => r.json());
const filtered = allProducts.filter(p => p.category === selected);

FIX: filter on the server. Fetch only what you need.

// GOOD: server-side filtering
// app/products/page.tsx (Server Component)
export default async function ProductsPage({ searchParams }) {
  const { category } = await searchParams;
  const products = await db.query.products.findMany({
    where: category ? eq(products.category, category) : undefined,
    limit: 20,
  });
  return <ProductGrid products={products} />;
}

PITFALL:MISSING_LOADING_STATES

SYMPTOM: page appears frozen during navigation. No visual feedback during data fetching.
FREQUENCY: common. LLMs focus on the final rendered state, not transitions.

FIX: add loading.tsx to every route segment that fetches data.
FIX: Suspense boundaries for independent data fetches.
FIX: Skeleton components that match final layout dimensions (prevent CLS).


PITFALL:WRONG_NEXTJS_VERSION_PATTERNS

SYMPTOM: code uses patterns from wrong Next.js version. Errors or unexpected behavior.
FREQUENCY: very common with LLMs trained on mixed documentation.

Wrong Pattern Correct Pattern (Next.js 15 + App Router)
getServerSideProps async Server Component
getStaticProps async Server Component + revalidate
_app.tsx / _document.tsx app/layout.tsx
pages/api/*.ts app/api/*/route.ts
import { useRouter } from "next/router" import { useRouter } from "next/navigation"
router.query await params or await searchParams
getLayout pattern nested layout.tsx files
getInitialProps Server Component + fetch

PITFALL:TAILWIND_V3_IN_V4_PROJECT

SYMPTOM: classes not working. Build warnings. Deprecated utility names.
FREQUENCY: common with LLM-generated code trained on Tailwind v3.

Tailwind v3 (WRONG) Tailwind v4 (CORRECT)
bg-gradient-to-r bg-linear-to-r
flex-shrink-0 shrink-0
flex-grow grow
decoration-clone box-decoration-clone
tailwind.config.js @theme in CSS
@apply (overuse) Direct utility classes
tailwindcss-animate plugin tw-animate-css import

FIX: run npx @tailwindcss/upgrade to auto-migrate.
FIX: check Tailwind v4 upgrade guide for full list of renames.


PITFALL:IGNORING_ACCESSIBILITY

SYMPTOM: no keyboard navigation, no screen reader support, contrast failures, no alt text.
FREQUENCY: very common with LLM-generated code. LLMs optimize for visual output, not accessibility.

TOP_MISSES:
1. <div onClick> instead of <button> — not keyboard accessible
2. Images without alt text
3. Form inputs without labels
4. Color as sole error indicator
5. Missing focus styles (outline: none everywhere)
6. No heading hierarchy
7. Modal without focus trap
8. Dynamic content not announced to screen readers

FIX: read accessibility.md before writing any component.
FIX: run axe-core in Playwright tests on every PR.
FIX: use shadcn/ui components — they have Radix accessibility built in.


PITFALL:OVER_MEMOIZATION

SYMPTOM: useMemo, useCallback, React.memo on everything. Code harder to read. No measurable perf gain.
FREQUENCY: moderate. Legacy React patterns before React Compiler.

ANTI_PATTERN: wrapping every function in useCallback "just in case".
ANTI_PATTERN: React.memo on every component.

// BAD: unnecessary memoization (React Compiler handles this)
const handleClick = useCallback(() => {
  setCount(c => c + 1);
}, []);

const title = useMemo(() => `Count: ${count}`, [count]);

FIX: React 19 Compiler auto-memoizes. Remove manual useMemo/useCallback unless profiling shows specific need.
FIX: if you find a performance problem, profile first (React DevTools Profiler), then optimize specifically.


LLM_SPECIFIC_MISTAKES

COMMON_LLM_ERRORS:
1. Generating Pages Router code for App Router project
2. Using getServerSideProps instead of async Server Component
3. Adding "use client" to every file
4. Using useEffect for data fetching
5. Importing from next/router instead of next/navigation
6. Not awaiting params and searchParams in Next.js 15+
7. Creating barrel files (index.ts exports)
8. Using Tailwind v3 class names in v4 project
9. Missing error boundaries and loading states
10. No accessibility (div for buttons, no alt text, no labels)
11. Using useFormState (old name) instead of useActionState
12. Using .errors instead of .issues on ZodError (Zod v4)

MITIGATION: every agent reads this pitfalls page before writing frontend code.


SELF_CHECK

BEFORE_EVERY_PR:
- [ ] no unnecessary "use client" directives?
- [ ] no barrel file imports?
- [ ] no useEffect for initial data fetching?
- [ ] no Pages Router patterns (getServerSideProps, _app.tsx)?
- [ ] error boundaries in place?
- [ ] loading states for all data fetches?
- [ ] accessibility basics (buttons, labels, alt text, contrast)?
- [ ] Tailwind v4 class names (not v3)?
- [ ] params/searchParams awaited?
- [ ] no manual memoization without profiling evidence?