How the Two Projects Connect
There are three HTTP channels between frontend and backend, each with its own purpose and auth shape:
- Public REST (
NEXT_PUBLIC_API_URL, defaulthttp://localhost:4000). Unauthenticated reads (GET /blog/posts,GET /items, etc.). Used bylib/api.ts'sapiclient. Safe to call from anywhere, but in this template we only call it from RSC. - Authed REST (same base URL,
Authorization: Bearer <session-token>). Authenticated reads + mutations (POST /items,POST /users/me/delete-account,GET /admin/*). Used byserverApi()from RSC and Server Actions.serverApi()reads the NextAuth httpOnly session cookie vianext/headersand forwards it as a Bearer header. The token never reaches client JavaScript. - Internal REST (
INTERNAL_API_URL+X-Internal-Token). NextAuth adapter operations:/internal/auth-adapter/*. 14 endpoints covering every method the NextAuth Adapter contract requires (createUser,getSessionAndUser,useVerificationToken, etc.). Used byinternalApi()and locked behindInternalApiTokenGuardon the backend (timing-safe compare againstINTERNAL_API_TOKEN).
The openapi.json file at the repository root is the canonical contract. The backend emits the spec at boot; CI fails on drift against the committed snapshot. The frontend regenerates types/api.d.ts from this file via npm run generate-types; the same CI step also fails if the generated file is stale.
Magic-link email split
NextAuth's email sign-in splits across the two processes on purpose. Storage is delegated to the backend through the adapter (createVerificationToken writes, useVerificationToken consumes atomically). Send stays on the frontend, where NextAuth's Nodemailer provider sends synchronously inside the request that initiated sign-in: latency matters there, and a queue would be a regression.
Bulk and transactional email is the other path entirely: the backend queue plus its cron worker. Both are covered in Email.