Routing & auth wiring
TanStack Router routes, Instant auth context, and navigation guards.
Routing & auth wiring
File: src/router.jsx
Routes
| Path | Behavior |
|---|---|
/ | If signed in → Planner. If signed out → Landing. |
/home | Always Landing (marketing). Signed-in CTAs open the planner; signed-out keep guest/login. |
/login | Magic-code login. Authenticated users redirect to /. |
/s/$token | Public shared planner. No auth guard. |
/account | Account page. Signed-out visitors redirect to /login. |
Root layout only shows a compact error status if auth fails. While Instant auth is loading, routes still paint — / uses a compact status only when no cached user is restored yet; /login, /home, and share links render immediately.
Eager vs lazy routes
Landing and Planner are lazy so signed-in cold loads do not pull marketing JS (and signed-out loads skip the planner bundle). Compact BootStatus is the defaultPendingComponent while chunks resolve. Login, Account, and SharedPlanner stay lazy as well.
Boot sequence (signed-in /)
Avoid stacking blank full-page “불러오는 중…” gates. Intended order:
- Auth —
/shows a compact status whileauth.isLoading; other routes are not gated. - Planner chrome — cold empty workspace still mounts a light header shell; once the board list has rows, the full header stays mounted (list board drives the shell while detail hydrates).
- Surface — Prefer an empty grid from the list board while detail hydrates.
surfacePendingonly when there is no list row yet. Prefs may fill in after paint; they must not unmount chrome. - Viewer banner —
shouldShowViewerBanner/roleKnowngate the 보기 전용 bar until owner/editors links are resolved (no owner flash).
Helpers: src/board/workspace-loading.js, roleKnown in src/sharing/member-policy.js.
Guest sign-in keeps the landing CTA spinner until auth resolves, then the planner paints an optimistic board shell (empty grid) while ensureWorkspace seeds — presence joins only once a real board id exists. Todo badge subscription is unchanged.
Auth context
const auth = db.useAuth();
// mapped into router context; router.invalidate() when loading/user changesGuards re-run when auth changes because the router is invalidated. Shared links are not gated in the router — password gating lives in useSharedBoard / share-access.js.
Landing vs login
- Landing (
/when signed out, always on/home) — marketing preview, guest sign-in (db.auth.signInAsGuest()), theme toggle (local only). Signed-out: link to/login. Signed-in: “시간표 열기” →/. - Login — email → 6-digit code via
useMagicCodeAuth.
PWA and SPA fallback
vercel.json rewrites non-API paths to /index.html. Do not enable cleanUrls — it breaks SPA fallback (guarded by test/vercel-spa.test.js). The service worker navigate fallback explicitly denies /api/*.