Performance: Smarter Static Generation in v1.0.86
Performance: Smarter Static Generation in v1.0.86
PERF-22 · v1.0.86 · PR #62
Overview
v1.0.86 ships targeted rendering-strategy fixes that reduce unnecessary serverless function invocations on the dashboard navigation shell and the /sign-up route. No end-user behaviour changes.
Background: Next.js rendering modes
Next.js App Router supports three rendering modes for route segments:
| Directive | Behaviour |
|---|---|
| (default) | Static or ISR depending on data-fetching |
force-dynamic | Always server-rendered on every request (SSR) |
force-static | Always pre-rendered at build time (SSG) |
Using force-dynamic on a segment that has no dynamic data is wasteful — it spins up a serverless function for each request when a cached or pre-built response would do.
What changed
1. Dashboard layout shell — force-dynamic removed
The dashboard layout shell (src/app/dashboard/layout.tsx) previously carried a blanket force-dynamic export. This caused the layout segment to be server-rendered on every navigation, even though:
- The shell contains only
Linkelements and"use client"components. - Session data (
useSession,OrgSwitcher,UserMenu) is fetched client-side. - No
auth()call is made server-side inside the layout itself.
Removing force-dynamic from the layout allows Next.js to treat the navigation shell as a cacheable segment. Each dashboard child page that genuinely requires server-side auth (auth() call) already exports its own force-dynamic — those pages are unaffected.
// Before — src/app/dashboard/layout.tsx
export const dynamic = 'force-dynamic'; // ← removed
import Link from "next/link";
// ...
// After — src/app/dashboard/layout.tsx
import Link from "next/link";
// No segment-level dynamic export; child pages control their own rendering.
2. Sign-up page — marked force-static
The /sign-up route is a compile-time redirect to /sign-in. It has no dynamic data, no session access, and no database queries. Without an explicit directive it could be subject to unnecessary dynamic evaluation.
Adding force-static ensures it is pre-rendered at build time:
// src/app/sign-up/[[...sign-up]]/page.tsx
export const dynamic = "force-static";
import { redirect } from "next/navigation";
/** Sign-up is handled by OAuth providers — redirect to sign-in. */
export default function SignUpPage() {
redirect("/sign-in");
}
3. Middleware — /api/health moved to matcher exclusion
Previously the middleware function contained an explicit if-block to bypass auth checks for /api/health and /api/health/*. These entries have been removed in favour of excluding the path at the matcher level:
// src/middleware.ts — config.matcher
'/((?!_next/static|_next/image|favicon\.ico|tailwindcss-browser\.js|api/health).*)'
Excluding the path from the matcher entirely means the middleware function is never invoked for health-check requests, which is marginally more efficient and keeps the allow-list simpler.
Rendering strategy reference
For completeness, the full rendering strategy across key routes after this release:
| Route | Strategy | Directive |
|---|---|---|
/ | ISR (60 s) | revalidate = 60 |
/pricing | ISR (1 h) | revalidate = 3600 |
/sign-in | Static | force-static |
/sign-up | Static | force-static ✨ new |
/dashboard (shell) | Cacheable shell | (default, removed force-dynamic) ✨ new |
| Dashboard child pages | Dynamic (SSR) | force-dynamic (per-page) |
Impact
- Fewer cold-starts on the dashboard navigation shell and
/sign-up. - Simplified middleware allow-list — one fewer branching condition per request.
- No behaviour change — auth-protected dashboard pages remain fully dynamic.