[1/2] Port frontend to Next.js 16 (App Router)#735
Merged
RSkuma merged 4 commits intohackforla:developfrom May 7, 2026
Merged
Conversation
Removes 10 docs that were either committed-as-drafts and never filled in (containing literal [INSERT-...] placeholders or marked **_DRAFT NOT YET FILLED OUT_**), or self-flagged # OUTDATED while describing a previous project layout that no longer exists. Also cleans up direct references to the deleted files: - 10 corresponding nav entries removed from mkdocs/mkdocs.yml - 3 broken role links removed from joining-the-team/intro.md - 2 broken bullets removed from resources.md (renumbered) - 2 broken Additional Resources entries removed from developer/installation.md - 1 sentence pointing at the deleted Frontend Architecture guide removed from developer/backend.md Verified with `mkdocs build --strict`. Remaining info-level link warnings are pre-existing issues unrelated to this PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns the dev docs with the engineering-assessment direction: Next.js + existing Django backend, PeopleDepot integration deferred to Stage 2, Cognito ID-token JWT, CSS Modules, three-container ECS task with Postgres-in-container. Flattens mkdocs/docs/ to docs/ at repo root and drops mkdocs entirely: no more mkdocs.yml, mkdocs-build workflow, docker-compose.docs.yml, or the three mkdocs-*.md docs about the doc system itself. GitHub renders the markdown directly. Folds in audit corrections from doc-by-doc review: Stage 1 / Stage 2 phasing, em-dash sweep, WHY-notes pattern, /demo route dropped, development-culture.md merged into CONTRIBUTING.md, the-team.md and index.md dropped (content promoted to README.md). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the legacy Vite + React 18 frontend toolchain with a Next.js 16 App Router scaffold, and split the monolithic stage image into separate Next.js and Django containers. Frontend toolchain: - Next 16 + React 19 + TypeScript 5 - Tailwind 3.4 + SCSS preserved from legacy (PR2 will fold into CSS Modules) - Vitest + jsdom + Testing Library replacing Jest - SVGR for both Webpack and Turbopack with svgo: false to preserve clip-path id references - vite-plugin-svgr in vitest config for test-time SVG imports Infra: - dev/next.dockerfile replaces dev/vite.Dockerfile (port 3000) - stage/next.dockerfile + stage/django.dockerfile replace the combined stage/Dockerfile that baked the Vite build into Django - docker-compose.stage.yml runs three services (pgdb on 5433, django on 8000, next on 3000) with BACKEND_INTERNAL_URL= http://django:8000 driving Next's /api/* and /admin/* rewrites Cleanup: - Drop .github/workflows/build-deploy-stage.yml and aws/task-definition.json - the deploy pipeline now lives in hackforla/incubator - Anchor the legacy 'data' and 'media' gitignore rules to the repo root so they don't shadow per-feature data/ directories the port introduces - README + docs/developer/design-system.md updated to reflect the Next.js direction Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move all of the React/TS source, tests, and assets from the
Vite + react-router-dom layout into the Next.js 16 App Router,
restructured around features/, shared/, and route shims under
src/app/.
Routing:
- Route groups (with-nav)/ and (auth)/ replace the legacy
HomeLayout / DefaultNavLayout / AuthNav split. Group layouts
hold the navbar wiring; page.tsx files are thin shims that
delegate to the matching feature.
- /privacypolicy renamed to /privacy-policy (hyphenated).
- /demo and /demo-tailwind dropped.
Features:
- features/landing/, credits/, qualifier/, session/,
privacy-policy/, not-found/ each own their components, data,
and a FEATURE_MAP.md describing the entry point and layer
dependencies.
- shared/components/ deduplicates the legacy components/ and
tw-components/ split (Tailwind variants kept where both
existed).
- shared/icons/ and shared/images/ are SVGR-imported; PNGs in
public/ for unprocessed cases.
Library swaps for React 19 compatibility:
- react-popper -> @floating-ui/react (Dropdown)
- react-transition-group -> mount/unmount (TransitionWrapper);
PR2 will reintroduce CSS-driven fade transitions
- TextField generic over FieldValues (was pinned to
{ password: string } in legacy)
Tests:
- 12 ported to Vitest + Testing Library
- TextField.test.tsx and LandingPage.test.tsx skipped with
inline notes (component shape changed; selectors stale)
Hydration / SSR fixes from smoke testing:
- CookieBanner reads cookies in useEffect after mount-then-
render to avoid a server/client tree mismatch
- QualifiersContext hydrates localStorage in useEffect
- Logo SVG sizing uses h-{N} w-auto to override SVGR's baked-in
width/height attributes (same fix applied to the credits
high-five illustration)
- LandingPageCop modal styles converted from a dead
_LandingPageCop.scss tree to inline Tailwind utilities
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Port frontend to Next.js 16 (App Router)
Branch:
next-rewritestacked ondocs-rewrite(#734).Retarget to
developonce #734 lands.Summary
Replaces the legacy
frontend/(Vite 5 + React 18 + TypeScript 4 + react-router-dom + SCSS + a parallel Tailwind migration) with a Next 16 (App Router) + React 19 + TypeScript 5 + Tailwind 3.4 surface. Behavior preserved; styling carried forward intact so PR2 can sweep Tailwind classes and SCSS into co-located CSS Modules in a clean, isolated diff.This is PR1 of a 2-PR chain. PR2 is Tailwind/SCSS → CSS Modules.
Why this shape
Commits
What's in this PR
Stack
create-next-app@latestoutput.frontend/tailwind.config.ts. Removed in PR2.src/shared/styles/; component-paired files alongside their components.Architecture
src/app/is route shims only;src/features/<feature>/owns real work;src/shared/is cross-feature primitives.(with-nav)and(auth)route groups hold the two layout shapes.frontend/ARCHITECTURE_MAP.mddocuments layer rules, route-shim policy, controller-API policy, function-style convention, and a route-to-entry table.FEATURE_MAP.md(landing, credits, qualifier, session). privacy-policy and not-found are trivial enough to skip the map.Routes after the port
/features/landing/components/LandingPage/creditsfeatures/credits/components/CreditsPage/privacy-policyfeatures/privacy-policy/PrivacyPolicyPage/qualifier/[page]features/qualifier/components/QualifierConsole/loginfeatures/session/components/LoginForm/signupfeatures/session/components/SignupFormfeatures/not-found/NotFoundPageNotable per-component changes
/demo,/demo-tailwind(legacy showcase, no longer needed), thetw-components/↔components/duplication (tw versions win where both existed),App.tsx,index.tsx,Router.tsx(Next App Router replaces them).IconButton:iconUrl: string→Icon: ComponentType<SVGProps>so the SVGR-imported component can be passed directly.TextField: now generic over a react-hook-form value schema; legacy version was pinned to{ password: string }and broke at type level for any non-password field.Dropdown: react-popper → @floating-ui/react. Outside-click handling moves intouseDismiss; removes ~25 lines of manualdocument.bodyevent wiring.TransitionWrapper: react-transition-group → mount/unmount. Visual fade is intentionally absent until PR2 reintroduces it via CSS Modules.CookieBanner: cookie read deferred touseEffectafter a mount-then-render gate so server and client trees match (legacy ran the read at state init, which produced a hydration mismatch on any cookied visit).QualifiersContext: hydrates fromlocalStorageviauseEffect, not at state init — SSR can't read localStorage.h-{N}orh-autoto override their baked-inwidth/heightattributes; CSS percentage widths alone don't propagate to height the way<img>does./privacypolicy→/privacy-policy(hyphenated; standard URL casing).SVG handling
@svgr/webpackconfigured innext.config.tsfor both Webpack and Turbopack withsvgo: falseso SVGO's defaultcleanupIdsdoesn't break clip-path id references in the logo files.?react/?urlquery suffixes are gone. Where the legacy used?urlfor CSS background images (privacy-policy bg, landing page bg), the SVG file moved topublic/svgs/and is referenced as a path string — SVG-as-React-component cannot be used insideurl(...).Dev infra
dev/vite.Dockerfile→dev/next.dockerfile.docker-compose.yml:viteservice →nextservice, port 5175 → 3000,develop.watchcleaned to ignorenode_modules/and.next/.README.md,docs/developer/design-system.md) bumped Next.js 15 → 16.Stage infra (folded in)
stage/Dockerfile(combined image that baked the Vite build into Django) split intostage/next.dockerfile+stage/django.dockerfile.docker-compose.stage.ymlruns three services: pgdb (5433:5432), django (8000), next (3000), withBACKEND_INTERNAL_URL=http://django:8000driving Next's/api/*and/admin/*rewrites so the local stage matches the deployed stage's single-origin shape..github/workflows/build-deploy-stage.ymlandaws/task-definition.jsondeleted — the deploy pipeline lives inhackforla/incubatornow.These were originally tagged as post-merge cleanup in #734 but are pulled forward here to keep dev → stage → deploy on the same image shape.
Misc
.gitignorerulesdataandmediaare now/dataand/media. The unanchored versions matched any directory nameddata/mediaanywhere, which would silently hide the new per-featuredata/directories that the port introduces.What's deferred
.github/workflows/lint.yml+ delete legacylinter.yml(post-merge cleanup).jest-react-test.ymlworkflow rename (still labeled "Jest" but runs Vitest now — functional but stale).NEXT_PUBLIC_API_URLindev/dev.envis cross-origin; with the rewrite-based stage shape, dev should probably use the same relative-URL pattern. Not addressed here._NotFoundPage.scss(breakpoint-media-max("smtablet")style is deprecated). Pre-existing; warns but compiles.TextFieldandLandingPagetest rewrites (describe.skipwith inline notes — the underlying components changed shape during the port).Test plan
cd frontend && npm install— lockfile is committed; deps install clean.npm run build— production build succeeds, all 7 routes compile.docker compose up— pgdb, django, next services come up; frontend onlocalhost:3000./renders landing hero, mission section, CoP cards/creditstoggles between Illustrations and Iconography; high-five illustration sized correctly/privacy-policyrenders with the bg-top illustration on lg screens/loginrenders the form with side illustration; bad email shows validation error/signuprenders four fields + form-level validation/qualifier/1shows CoP cards; selecting one enables Continue/qualifier/2shows skill matrix; back/forward through skill batches works/qualifier/3shows weekly availability calendar; timezone dropdown opens and selects/foo(any unknown URL) shows the 404 with HeaderNav + FooterNavnpm test— 12 tests pass, 2 skipped (TextField, LandingPage with WHY notes).docker compose -f docker-compose.stage.yml up— pgdb (5433), django (8000), next (3000) come up;/api/*and/admin/*fromlocalhost:3000proxy to django via Next rewrites. Not yet smoke-tested.Stacking note
This PR is stacked on
docs-rewrite(PR #734). When #734 lands, retarget this PR's base todevelopand rebase. Squash-merge intodevelopis fine; the per-commit split (scaffold + port) is preserved here on the branch but does not need to land ondevelopas separate commits.