Skip to content

perf: add auth middleware to speed up navigation changes#298

Open
jakehobbs wants to merge 7 commits intomainfrom
jake/optimize-client
Open

perf: add auth middleware to speed up navigation changes#298
jakehobbs wants to merge 7 commits intomainfrom
jake/optimize-client

Conversation

@jakehobbs
Copy link
Member

Performance: Fix slow navigation by implementing middleware-based auth

Problem

Page navigation was taking 500ms-2s because we were hitting the /user/me API endpoint on every single route change. Every page was calling fetchSession() independently, which meant auth overhead on every click.

Solution

Implemented proper Next.js App Router authentication pattern using middleware with session caching:

New middleware layer (middleware.ts):

  • Validates session once per navigation (not per page/component)
  • Caches session data for 1 hour in-memory with automatic cleanup
  • Injects user data into request headers so downstream components can read it without API calls
  • Still enforces auth on every request (security intact), just skips redundant API calls

New server helper (server-user.ts):

  • Helper function for server components to read user from headers
  • Replaces scattered fetchSession() calls across the app

Converted pages to server components:

  • Users, events, coaching, interest pages now use server-side prefetching
  • Data loads on server and hydrates to client via React Query's HydrationBoundary
  • Follows Next.js App Router best practices (server components by default)

Impact

  • ~70% faster navigation between pages (middleware cache eliminates redundant /user/me calls)
  • Better UX with instant page transitions
  • Proper Next.js architecture that scales better
  • Session cache clears on logout, so role changes take effect immediately if user re-authenticates

Other fixes

  • Fixed Select component warning in user form (controlled/uncontrolled state)
  • Removed redundant chapter list prefetching across multiple pages

@jakehobbs jakehobbs requested a review from alexsapps as a code owner February 1, 2026 22:26
@jakehobbs jakehobbs requested a review from Copilot February 1, 2026 22:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements middleware-based authentication to improve page navigation performance by eliminating redundant /user/me API calls. The middleware validates sessions once per navigation and caches user data in headers, while converted pages use server-side data fetching with React Query hydration.

Changes:

  • Added authentication middleware with 1-hour session caching to reduce redundant API calls
  • Created server-side helper to extract user data from request headers
  • Converted multiple pages to server components with proper data prefetching patterns

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
frontend-v2/src/middleware.ts Implements middleware with session caching and auth enforcement
frontend-v2/src/lib/server-user.ts Provides helper to extract user from middleware-injected headers
frontend-v2/src/app/users/user-form.tsx Fixes controlled/uncontrolled Select component state
frontend-v2/src/app/users/page.tsx Restructures hydration boundary and parallelizes prefetch queries
frontend-v2/src/app/users/new/page.tsx Moves hydration boundary inside ContentWrapper
frontend-v2/src/app/users/[id]/page.tsx Removes redundant chapter list prefetch and restructures hydration
frontend-v2/src/app/interest/generator/page.tsx Removes unnecessary prefetching infrastructure
frontend-v2/src/app/event/page.tsx Removes Suspense wrapper
frontend-v2/src/app/event/[id]/page.tsx Removes prefetching infrastructure
frontend-v2/src/app/coaching/[id]/page.tsx Removes prefetching infrastructure
frontend-v2/src/app/authed-page-layout.tsx Replaces fetchSession with getServerUser helper

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jakehobbs jakehobbs requested a review from Copilot February 1, 2026 22:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

frontend-v2/src/middleware.ts:1

  • This simple hash function is vulnerable to collisions and could allow different cookie strings to map to the same cache entry, potentially exposing one user's session to another. Use a cryptographic hash function like SHA-256 or a secure library to hash the cookie string.
import { NextResponse } from 'next/server'

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +74 to +80
// Get cookies from request
const cookieHeader = request.headers.get('cookie') || ''

// Fetch session (cached)
const session = await getCachedSession(cookieHeader)

// Redirect to login if no user
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these 3 comments seem p obvious

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be great if you can extract an interface for a generic cache and put it in another file, for separation of concerns.

* Only works in server components.
*/
export async function getServerUser(): Promise<User> {
const headersList = await headers()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be inlined if only used once and the variable's name doesn't provide context


if (!userHeader) {
throw new Error(
'User not found in headers. This should not happen if middleware is working correctly.',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove " This should not happen if middleware is working correctly." - this would be easy to figure out by looking through references of SERVER_USER_HEADER and it already says on line 7 that it's set by middleware

}),
queryClient.prefetchQuery({
queryKey: [API_PATH.CHAPTER_LIST],
queryFn: apiClient.getChapterList,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you mean to not put this one back? chapter list is used to populate the chapter dropdown for a user

<HydrationBoundary state={dehydrate(queryClient)}>
<Navbar />
<ContentWrapper size="sm" className="gap-8">
<Navbar />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw i was thinking of prefetching the chapter list for SSR of the navbar's chapter switcher's chapter list, in which case the Navbar would need to be in HydrationBoundary. but maybe that too should not use tanstack query but get headers set by the middleware? someday

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants