# Next.js Local Auth + JSON DB Pattern

When deploying a Next.js app on a Chinese cloud server with GFW restrictions, Clerk/Supabase may be unreachable or undesirable. Replace them with local-only alternatives.

## Files to Change (reference implementation)

### 1. `src/lib/auth.ts` — Cookie-based password auth

```typescript
import { cookies } from 'next/headers';

const COOKIE_NAME = 'icoach_auth';
const PASSWORD = process.env.ICOACH_PASSWORD ?? 'default';

export async function setAuthCookie(password: string): Promise<boolean> {
  if (password !== PASSWORD) return false;
  const store = await cookies();
  store.set(COOKIE_NAME, '1', {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax', maxAge: 60 * 60 * 24 * 30, path: '/',
  });
  return true;
}

export async function getAuthUserId(): Promise<string | null> {
  const store = await cookies();
  const token = store.get(COOKIE_NAME);
  return token?.value === '1' ? 'local-user' : null;
}

// For API routes (uses headers(), not cookies())
export function getAuthFromRequest(request: Request): string | null {
  const cookieHeader = request.headers.get('cookie') ?? '';
  const match = cookieHeader.match(/icoach_auth=([^;]+)/);
  return match?.[1] === '1' ? 'local-user' : null;
}
```

### 2. `src/lib/db.ts` — JSON-file database

```typescript
import * as fs from 'node:fs';
import * as path from 'node:path';

const DB_PATH = path.join(process.cwd(), 'data.json');

function readDb() {
  if (fs.existsSync(DB_PATH)) return JSON.parse(fs.readFileSync(DB_PATH, 'utf-8'));
  return { users: {}, sessions: [] };
}

function writeDb(data: any) {
  fs.writeFileSync(DB_PATH, JSON.stringify(data, null, 2));
}

// Export async functions matching the old supabase.ts interface
export async function getOrCreateUser(userId: string) { /* ... */ }
export async function getSessionsByUser(userId: string) { /* ... */ }
export async function createSession(userId: string, isFirst: boolean) { /* ... */ }
export async function closeSession(sessionId: string, metadata: any) { /* ... */ }
```

### 3. `src/proxy.ts` — Middleware (replaces `clerkMiddleware`)

```typescript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const PROTECTED = ['/session', '/star', '/home'];

export default function proxy(request: NextRequest) {
  const isProtected = PROTECTED.some(p => request.nextUrl.pathname.startsWith(p));
  if (!isProtected) return NextResponse.next();
  const token = request.cookies.get('icoach_auth');
  if (token?.value !== '1')
    return NextResponse.redirect(new URL('/sign-in', request.url));
  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!_next|api|.*\\.(?:html?|css|js|jpe?g|webp|png|gif|svg|ttf|woff2?|ico)).*)'],
};
```

### 4. `src/app/layout.tsx` — Remove ClerkProvider

Simply remove the `<ClerkProvider>` wrapper. Everything else stays the same.

### 5. `src/app/api/auth/login/route.ts` — Login endpoint

```typescript
import { NextRequest, NextResponse } from 'next/server';
import { setAuthCookie } from '@/lib/auth';

export async function POST(req: NextRequest) {
  const { password } = await req.json();
  const ok = await setAuthCookie(password);
  if (!ok) return NextResponse.json({ error: 'Invalid password' }, { status: 401 });
  return NextResponse.json({ ok: true });
}
```

### 6. Sign-in page — Simple password form

Replace the Clerk `<SignIn />` component with a password input form that POSTs to `/api/auth/login`.

### 7. API routes — Replace `auth()` with cookie check

```typescript
// OLD: import { auth } from '@clerk/nextjs/server';
// OLD: const { userId } = await auth();
// NEW:
import { getAuthFromRequest } from '@/lib/auth';
const authUserId = getAuthFromRequest(req);
if (!authUserId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
```

### 8. Server pages — Replace `auth()` with cookie check

```typescript
// OLD: import { auth, currentUser } from '@clerk/nextjs/server';
// OLD: const { userId } = await auth();
// OLD: const clerkUser = await currentUser();
// NEW:
import { getAuthUserId } from '@/lib/auth';
const userId = await getAuthUserId();
// Use a default name instead of clerkUser.firstName
```

### 9. Cleanup

```bash
npm uninstall @clerk/nextjs @supabase/ssr @supabase/supabase-js
npm install  # sql.js if needed, otherwise zero new deps
```

## Pitfalls

- **`better-sqlite3` fails on Chinese servers**: Native compilation from source times out or fails. Use pure-JS alternatives (`sql.js`) or the JSON-file pattern above.
- **`sql.js` WASM init fails in Next.js SSR**: The WASM binary may not load correctly in the server-side environment. The JSON-file approach avoids this entirely.
- **Next.js 16 `proxy.ts` naming**: The middleware file is `proxy.ts` (not `middleware.ts`) and the export must be `export default function proxy()`.
- **`.env.local` for password**: Use `ICOACH_PASSWORD=xxx` — this file is gitignored by default in Next.js.
- **Cookie `httpOnly`**: Set to `true` in production so JS can't read the auth cookie.
