Architecture
The frontend uses a split architecture: Next.js App Router owns routing, layouts, and server entrypoints in app/, while product code lives in src/ using Feature-Sliced Design.
Layering
The repo-level rule is:
shared -> entities -> features -> widgets -> app/
That rule shows up in the filesystem as follows.
| Layer | What belongs here |
|---|---|
src/shared | API primitives, env config, generic UI, providers, and cross-cutting utilities |
src/entities | Domain types, schemas, query helpers, generated clients, and entity UI when an entity truly needs it |
src/features | User-facing actions such as login, onboarding, student management, and report generation |
src/widgets | Higher-level compositions such as the landing header, student sidebar, and auth modal |
app | Route tree, layouts, loading states, error boundaries, route handlers, and redirects |
Root-level exceptions are intentional:
components/ui/contains the forked shadcn primitiveshooks/use-mobile.tsandlib/utils.tsstay at the repo root becausecomponents.jsonexpects them there
App Router Boundary
app/ is kept thin. Its main responsibilities are:
- grouping routes with layouts such as
(marketing),(auth), and(onboarding) - enforcing access control in server layouts such as
app/admin/layout.tsx - exposing route handlers such as
app/api/[...path]/route.ts - defining loading and error boundaries close to the route tree
Most business logic is imported from src/ rather than implemented inline in pages.
Most authenticated page.tsx files also stay thin and hand off to client feature islands. The server boundary is used more for auth, metadata, and shell concerns than for a blanket authenticated SSR-prefetch rollout.
Data-Access Boundary
The frontend has three distinct access paths:
| Path | Used for | Main files |
|---|---|---|
Browser fetch through /api | Normal client-side product requests | src/shared/api/base.ts, src/shared/api/orval-mutator.ts, app/api/[...path]/route.ts |
| Server fetch during SSR or server layouts | Auth checks and server-only reads | src/shared/api/serverFetch.ts, src/entities/session/api/getServerUser.ts |
| Public server reads | Tenant homepage and public academy lookups | entity-level serverFetch helpers used from route pages |
The browser always targets ENV.API_URL, which is hardcoded to /api. That keeps auth cookie handling, tenant header propagation, refresh retry, and error normalization in one frontend-controlled boundary.
Source Paths
| Contract | Source path |
|---|---|
| Route groups, protected layouts, and route handlers | lumie-frontend/app/** |
| Same-origin proxy | lumie-frontend/app/api/[...path]/route.ts |
| Browser fetch primitives | lumie-frontend/src/shared/api/base.ts, lumie-frontend/src/shared/api/orval-mutator.ts |
| Server auth fetch | lumie-frontend/src/entities/session/api/getServerUser.ts, lumie-frontend/src/shared/api/serverFetch.ts |
| Query client lifecycle | lumie-frontend/src/shared/lib/query-client.ts, lumie-frontend/src/shared/providers/QueryProvider.tsx |
| Session side channel for shared API code | lumie-frontend/src/shared/api/sessionAccessor.ts, lumie-frontend/src/shared/lib/sessionCache.ts |
Query and Session Architecture
TanStack Query is initialized once in QueryProvider. getQueryClient() creates:
- a per-request query client on the server to avoid cross-request cache leakage
- one long-lived browser query client for client navigation
Session state is intentionally split:
getServerUser()handles route protection in server layoutsuseMe()anduseMeQuery()expose the authenticated user in client codesessionAccessorplussessionCachelet shared API code read or clear tenant session data without breaking FSD layer rules
UI Composition
The main shells are built as widgets or route-local shells rather than global monolith components:
- marketing pages use
app/(marketing)/layout.tsx - admin pages use
AdminShell - dashboard pages use
StudentSidebar,Header, and the shared sidebar primitives
This keeps route-specific layout choices close to their route group while leaving reusable product logic inside src/.
Verification
cd lumie-frontend
rg -n "getServerUser|serverFetch|sessionAccessor|sessionCache|getQueryClient|QueryProvider" \
app src
npx tsc --noEmit
Success means the grep finds the route-protection, server-fetch, session-cache, and query-client boundaries, and npx tsc --noEmit completes without TypeScript errors.