Senior Tips & Tricks

A grab-bag of hard-won App Router wisdom: the mistakes seniors stop making and the small moves that pay off daily.

A closing collection of the things that separate someone who uses the App Router from someone who is fluent in it. None are big; together they save you days.

Server/Client composition

  • A Client Component cannot import a Server Component — but it can receive one as children (or any prop). Pass server-rendered UI into client wrappers instead of trying to import it.
  • Props crossing the boundary must be serializable — no functions, class instances, or Dates-as-methods. Pass Server Actions (which are serializable references), not arbitrary callbacks.

Data & caching reflexes

  • Wrap a per-request fetch in React's cache() to dedupe it across a render, the same way fetch memoizes automatically.
  • After a mutation, prefer revalidateTag over blowing away whole paths — it invalidates exactly what changed.
  • Fetch data in parallel by default; only sequence when one call genuinely depends on another's result.

Correctness traps

  • Import useRouter and friends from next/navigation, never the Pages-era next/router.
  • await params, searchParams, cookies(), and headers() — they are all async in Next 15.
  • redirect() and notFound() work by throwing, so never wrap them in a try/catch that swallows the control-flow throw.
  • Metadata: prefer the static metadata export or async generateMetadata over hand-writing tags — it dedupes and merges correctly across nested layouts.

Workflow

Read the build output every time, keep a Data Access Layer as your single source of authorization, and colocate a route's page, loading, error, and actions so the whole feature is one folder you can reason about at a glance.

Example

Example · typescript
// Pattern: pass a Server Component through a Client wrapper as children.
// The client tabs handle interactivity; the panels stay server-rendered
// and ship zero JS.

// app/page.tsx  (Server Component)
import { Tabs } from './tabs';        // 'use client'
import { ServerReport } from './report'; // async Server Component

export default function Page() {
  return (
    <Tabs>
      {/* Server-rendered content injected as children — allowed */}
      <ServerReport />
    </Tabs>
  );
}

// tabs.tsx
'use client';
import { useState } from 'react';
export function Tabs({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(true);
  return (
    <div>
      <button onClick={() => setOpen((o) => !o)}>Toggle</button>
      {open && children}
    </div>
  );
}

When to use it

  • A senior avoids importing a Server Component into a Client Component by passing it as a children prop instead.
  • A developer uses the useSelectedLayoutSegment hook to highlight the active section in a shared layout without prop drilling.
  • A team colocates private components with the route file inside app/ using underscore-prefixed folder names to keep things tidy.

More examples

Server component as children prop

Passing a Server Component as children to a Client Component keeps it server-rendered even though the parent is a client.

Example · ts
// ClientShell.tsx
'use client';
export default function ClientShell({ children }: { children: React.ReactNode }) {
  return <div className="shell">{children}</div>;
}

// app/page.tsx  (Server Component)
import ClientShell from './ClientShell';
import ServerContent from './ServerContent';

export default function Page() {
  return (
    <ClientShell>
      <ServerContent /> {/* stays a Server Component */}
    </ClientShell>
  );
}

useSelectedLayoutSegment for nav

useSelectedLayoutSegment returns the active child segment name, enabling active link styling without URL comparison logic.

Example · ts
'use client';
import Link from 'next/link';
import { useSelectedLayoutSegment } from 'next/navigation';

const links = [{ href: 'overview', label: 'Overview' }, { href: 'settings', label: 'Settings' }];

export default function DashboardNav() {
  const segment = useSelectedLayoutSegment();
  return (
    <nav>
      {links.map(l => (
        <Link key={l.href} href={`/dashboard/${l.href}`}
          style={{ fontWeight: segment === l.href ? 'bold' : 'normal' }}>
          {l.label}
        </Link>
      ))}
    </nav>
  );
}

Private folder to hide from router

Folders starting with _ are excluded from the App Router, so you can colocate helper code without accidentally creating routes.

Example · bash
app/
└── dashboard/
    ├── page.tsx
    ├── _components/       # underscore = private folder
    │   ├── StatsCard.tsx
    │   └── ChartWidget.tsx
    └── _utils/
        └── formatDate.ts

Discussion

  • Be the first to comment on this lesson.