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 wayfetchmemoizes automatically. - After a mutation, prefer
revalidateTagover 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
useRouterand friends fromnext/navigation, never the Pages-eranext/router. awaitparams,searchParams,cookies(), andheaders()— they are all async in Next 15.redirect()andnotFound()work by throwing, so never wrap them in atry/catchthat swallows the control-flow throw.- Metadata: prefer the static
metadataexport or asyncgenerateMetadataover 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
// 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.
// 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.
'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.
app/
└── dashboard/
├── page.tsx
├── _components/ # underscore = private folder
│ ├── StatsCard.tsx
│ └── ChartWidget.tsx
└── _utils/
└── formatDate.ts
Discussion