---
title: "Next.js basePath — Auth Redirect Gotcha"
aliases: [nextjs-basepath, basepath-redirects, nextjs-basepath-auth]
tags: [nextjs, basepath, auth, msal, redirect, deployment]
sources:
  - "daily/2026-04-16.md"
created: 2026-04-16
updated: 2026-04-16
---

# Next.js basePath — Auth Redirect Gotcha

When a Next.js app is deployed under a sub-path (e.g., `/hp-prod-tracker`), all auth redirect URLs must include the `basePath` prefix. Hardcoded paths like `/login` or `/pending` silently resolve to the root domain — the redirect succeeds from Next.js's perspective but the browser navigates to the wrong URL, breaking the auth flow.

## Key Points

- **All redirect paths must include basePath**: `/hp-prod-tracker/login`, not `/login`
- Next.js's `router.push()` automatically prepends `basePath`, but **string redirects in middleware or API routes do not** — these require manual prefixing
- `Response.redirect()` and `NextResponse.redirect()` require the full path including basePath
- Sign-out redirects and pending-state redirects are the most common locations where this is missed
- The bug is environment-specific: works in local dev (where basePath is not active or the app is at root) but breaks in production

## Details

### Configuring basePath in Next.js

```js
// next.config.js
module.exports = {
  basePath: '/hp-prod-tracker',
}
```

With this set, `router.push('/login')` navigates to `/hp-prod-tracker/login`. But manual redirects bypass this:

```ts
// ❌ WRONG — in middleware.ts or API route
return NextResponse.redirect(new URL('/login', request.url));
// Redirects to https://domain.com/login (missing basePath)

// ✅ CORRECT
const basePath = '/hp-prod-tracker';
return NextResponse.redirect(new URL(`${basePath}/login`, request.url));
```

### Auth Flow Locations That Require Manual Fixing

1. **Middleware auth guard** — redirect to login if no session:
   ```ts
   // middleware.ts
   const BASE = process.env.NEXT_PUBLIC_BASE_PATH || '';
   return NextResponse.redirect(new URL(`${BASE}/login`, request.url));
   ```

2. **Sign-out handler** — after clearing session:
   ```ts
   // api/auth/signout route
   return NextResponse.redirect(new URL(`${BASE}/login`, request.url));
   ```

3. **Pending page redirect** — when org domain doesn't match:
   ```ts
   // If user.org domain not found → pending
   return NextResponse.redirect(new URL(`${BASE}/pending`, request.url));
   ```

4. **MSAL redirect URI** — must match the Azure Portal registration exactly:
   ```ts
   const msalConfig = {
     auth: {
       redirectUri: window.location.origin + '/hp-prod-tracker/login.html'
     }
   };
   ```

### Environment Variable Strategy

Use `NEXT_PUBLIC_BASE_PATH` (exposed to the browser) alongside `basePath` in next.config.js:

```bash
# .env.production
NEXT_PUBLIC_BASE_PATH=/hp-prod-tracker
```

```ts
// Shared utility
export const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? '';
export const redirect = (path: string) => `${BASE_PATH}${path}`;
```

This makes the basePath a single source of truth across middleware, API routes, and client code.

### Organization Domain Matching (SSO Context)

After successful Azure AD SSO, the application checks `organizations.domain` in the database to determine if the user belongs to an active organization. If no matching domain is found, the user is redirected to `/pending`. Ensure the `organizations` table has a row with `domain = 'oliver.agency'` (or whichever email domain the users log in with).

```sql
-- After first SSO deploy, verify this exists:
SELECT * FROM organizations WHERE domain = 'oliver.agency';
```

## Related Concepts

- [[wiki/concepts/msal-vanilla-js-pkce]] — MSAL.js client-side auth implementation details
- [[wiki/tech-patterns/azure-ad-msal-auth]] — Azure AD app registration and redirect URI configuration
- [[wiki/connections/graph-api-vs-msal-app-vs-delegated]] — auth flow selection context

## Sources

- [[daily/2026-04-16.md]] — HP Prod Tracker SSO migration (OAuthRelay → MSAL.js client-side); basePath issue discovered post-deploy where sign-out and pending redirects went to root domain instead of `/hp-prod-tracker/*`; fixed by prefixing all redirect strings with basePath
