Dev notes
Moving the authenticated hub from /protected to /, eliminating the redirect, and promoting sub-routes to top-level paths.
The original routing pattern had a problem: logged-in users who navigated to / were immediately redirected to /protected by the middleware. This meant the URL in the browser always reflected an implementation detail, not a meaningful destination. The hub at /protected was just an awkward artifact of how auth-gating was wired up, not a real route users would want to bookmark or share.
The fix is straightforward: make / context-aware. Unauthenticated visitors see the landing page. Logged-in users see the hub. Same URL, different content. Vitals and settings move to /vitals and /settings — paths that actually describe what they are.
The previous setup worked because /page.tsx had export const dynamic = "force-static". The landing page was baked into the CDN at build time and the middleware handled the redirect for authenticated users. No auth call touched the page component itself.
Once the root page needs to branch on authentication, force-static is gone. The page becomes a dynamic server component that calls auth0.getSession() on every request.
In practice this trade-off is negligible. auth0.getSession() does not make a network call — it decrypts the session cookie locally using the AUTH0_SECRET key. The cost is a few microseconds of CPU work. The hub content is still fetched client-side via TanStack Query exactly as before. For a personal portfolio, clean URLs are worth more than static generation on the root route.
The middleware still enforces auth — it just protects different paths now. /vitals and /settings get the same treatment /protected did: unauthenticated requests redirect to /auth/login?returnTo={pathname} before any page code runs. Authenticated requests go through auth0.middleware() for rolling session refresh. CSP headers are applied on every response, same as before.
The root / route is not middleware-gated because it intentionally renders for both states. There is nothing sensitive in the landing page for unauthenticated users and the hub content only renders after the server confirms a valid session. An unauthenticated request can never reach the hub code path.
src/proxy.ts — removed the / redirect block and the /protected protection block, added equivalent protection for /vitals and /settingssrc/app/page.tsx — converted from a static export to an async server component; now calls auth0.getSession() and renders either LandingContent or FeatureHubFeatureHub.tsx, vitals/*, and settings/* from the protected/ folder to their new top-level locationshref="/protected" to href="/" across feature pages and thoughts pagessrc/types/protected.ts renamed to src/types/hub.tsThe Express backend constructs the Google Calendar OAuth callback URL as {origin}/protected/settings?gcal=connected. After this migration the correct path is {origin}/settings?gcal=connected. Until the backend is updated, the ?gcal=connected success banner won't fire after connecting Google Calendar — but the middleware will catch the old URL gracefully. Deploy order: frontend first, then backend.