Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

104 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This is a Next.js project bootstrapped with create-next-app.

Auth flow & protected routes

Token storage strategy

Authentication uses a dual-storage strategy for the access token and a separate localStorage-only store for the refresh token:

Token Storage Purpose
access_token localStorage (auth_token) + cookie (auth_token) Attached as Authorization: Bearer on every API request. Cookie mirrors localStorage so Next.js middleware can check auth server-side without accessing localStorage (unavailable in middleware).
refresh_token localStorage (refresh_token) only Used exclusively by the token-refresh flow to obtain a new access token. Never sent to cookies or exposed to middleware.

Why not store the refresh token in a cookie? The refresh token is only consumed by the client-side JavaScript that calls /auth/refresh. Keeping it out of cookies reduces its exposure surface — middleware and server components never need it.

Silent token refresh

When any API request returns HTTP 401 with code: "ACCESS_TOKEN_EXPIRED", the client automatically:

  1. Calls POST /auth/refresh with the stored refresh token.
  2. Stores the new access token (and rotates the refresh token if the backend returns a new one).
  3. Retries the original request with the new token.

Concurrent 401s are handled via a single-flight pattern (src/lib/auth/refresh.ts): only one refresh request is ever in flight at a time. Requests that arrive while a refresh is pending are queued and resolved with the same new token once the refresh completes.

On refresh failure (invalid/expired refresh token):

  • All stored tokens are cleared.
  • The user is redirected to /login.
  • All queued requests are rejected.

Infinite refresh loops are prevented by the _retry flag on retried requests — a retried request that receives another 401 is thrown as an ApiError rather than triggering another refresh.

Relevant source files

File Role
src/lib/auth/token-store.ts Read/write/clear for both token types
src/lib/auth/refresh.ts Single-flight refresh logic
src/lib/api/client.ts HTTP client with 401 interceptor
src/lib/auth/session.ts Cookie helpers + middleware token reader
src/lib/auth/session.server.ts Server Component token reader

Protected routes

src/middleware.ts guards routes under /dashboard/* and /profile/*. Unauthenticated visitors are redirected to:

/login?redirect=/original-path

The login page reads redirect and sends the user to that path after sign-in (default: /dashboard).

Public routes

These paths are reachable without a token:

  • /
  • /login
  • /signup

Authenticated users who visit /login or /signup are redirected to /dashboard.

Getting Started

First, run the development server:

npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev

Open http://localhost:3000 with your browser to see the result.

You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.

This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.

CI checks

Frontend CI

Every pull request runs these checks in order — all must pass before merging:

Step Command What it catches
Lint npm run lint ESLint rule violations
Type check npm run typecheck TypeScript type errors
Unit tests npm run test Logic regressions
Build npm run build Compile-time errors, bad imports

Fixing failures locally

# Lint errors
npm run lint
# Auto-fixable issues
npx next lint --fix

# Type errors
npm run typecheck

# Test failures
npm run test

# Build errors
npm run build

Toast notifications

A global toast system (backed by Sonner) is mounted once in the root layout via <Toaster /> from @/components/ui/sonner — no per-page setup needed.

Feature pages (e.g. auth, settings) can trigger toasts either through the useToast() hook or the plain helper functions, whichever fits the component:

"use client";

import { useToast } from "@/hooks/use-toast";
// or: import { showSuccess, showError, showInfo } from "@/lib/toast";

export function SaveSettingsButton() {
  const toast = useToast();

  async function handleSave() {
    try {
      await saveSettings();
      toast.success("Settings saved");
    } catch {
      toast.error("Failed to save settings");
    }
  }

  return <button onClick={handleSave}>Save</button>;
}
  • Variants: success, error, info.
  • Toasts auto-dismiss after 4s by default (configurable via the duration prop on <Toaster />, or per-toast via the options argument).
  • Multiple toasts stack without overlapping (Sonner handles positioning).
  • Sonner renders toasts in an aria-live region, so they're announced to screen readers.

Learn More

To learn more about Next.js, take a look at the following resources:

You can check out the Next.js GitHub repository - your feedback and contributions are welcome!

Deploy on Vercel

The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.

Check out our Next.js deployment documentation for more details.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages