All posts
Next.js7 min read

Next.js App Router Patterns We Actually Use

Server Components, ISR, route groups, and the auth() pattern — a practical breakdown of the App Router features that delivered real value on a production-grade platform.

T

The BumpEvents Team

Why App Router?

The Pages Router is battle-tested and fine. But App Router unlocks Server Components — and once you understand Server Components, going back feels wasteful.

Here are the patterns we actually reached for.

Server Components as the default

Every page component is a React Server Component unless it needs interactivity. This means:

- Database queries happen on the server, zero client bundle impact

- No loading spinners for initial data — HTML arrives pre-rendered

- generateMetadata co-located with the page, not in a separate config

The only client components are those that genuinely need the browser: filter dropdowns, the RSVP button, the image upload widget, modals.

ISR for public pages

The discover page uses export const revalidate = 60. This means:

1. First request renders and caches the page

2. Subsequent requests serve the cache instantly

3. Every 60 seconds, one background regeneration request updates the cache

For an event platform where data changes occasionally but not constantly, this is the right trade-off. No need for 'use client' + SWR just to get "fresh enough" data.

The `auth()` pattern

NextAuth v5 provides a server-side auth() helper that works in Server Components, Route Handlers, and middleware. We use it like this:

// In a Server Component

const session = await auth()

if (!session) redirect("/login")

No more prop-drilling session objects. Each component that needs auth calls auth() directly — it's cached per-request by Next.js automatically.

Route Groups for layouts

The (marketing), (platform), (auth), and (dashboard) route groups let us have completely different layouts for different sections without affecting URLs. The marketing Navbar is different from the platform Navbar — no conditionals needed.

Parallel Data Fetching

On the event detail page, we need the event, the host profile, and the attendee list. Instead of awaiting them sequentially, we fire them all at once:

const [event, attendees] = await Promise.all([

getEvent(id),

getAttendees(id),

])

With Server Components, this is trivially clean — no Redux, no SWR, no context.

The takeaway

App Router is not hype. The combination of Server Components + ISR + auth() + Route Groups genuinely produces a codebase that is smaller, faster, and easier to reason about than the equivalent Pages Router implementation.