Skip to content

[1/2] Port frontend to Next.js 16 (App Router)#735

Merged
RSkuma merged 4 commits intohackforla:developfrom
Nickatak:next-rewrite
May 7, 2026
Merged

[1/2] Port frontend to Next.js 16 (App Router)#735
RSkuma merged 4 commits intohackforla:developfrom
Nickatak:next-rewrite

Conversation

@Nickatak
Copy link
Copy Markdown
Member

@Nickatak Nickatak commented May 2, 2026

PR: Port frontend to Next.js 16 (App Router)

Branch: next-rewrite stacked on docs-rewrite (#734).
Retarget to develop once #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

  • Stacked on docs-rewrite because the docs in Rewrite docs/ for future-state architecture #734 describe the target state we are building. Reviewer can read the spec alongside the code.
  • Two commits (scaffold + port) rather than dozens of granular ones. Scaffold = the empty Next.js shell + Docker infra; port = all React/TS/test/asset content moving in.
  • Tailwind preserved through the port (deferred to PR2) so visual surface does not move while the framework changes underneath.

Commits

# Subject
1 Scaffold Next.js 16 frontend and split stage Docker
2 Port legacy frontend to Next.js App Router

What's in this PR

Stack

  • Next.js 16 (App Router, default Turbopack for dev). Bumped from Next 15 in the docs to match create-next-app@latest output.
  • React 19, TypeScript 5.
  • Tailwind 3.4 (carried over). Theme + content paths in frontend/tailwind.config.ts. Removed in PR2.
  • SCSS (carried over). Tokens at src/shared/styles/; component-paired files alongside their components.
  • Vitest + jsdom + @testing-library + vite-plugin-svgr replacing Jest.
  • @floating-ui/react replacing react-popper (legacy not React-19 compatible).
  • next/font/local for the bundled Roboto family.

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.md documents layer rules, route-shim policy, controller-API policy, function-style convention, and a route-to-entry table.
  • Each feature dir has a FEATURE_MAP.md (landing, credits, qualifier, session). privacy-policy and not-found are trivial enough to skip the map.

Routes after the port

Route Entry
/ features/landing/components/LandingPage
/credits features/credits/components/CreditsPage
/privacy-policy features/privacy-policy/PrivacyPolicyPage
/qualifier/[page] features/qualifier/components/QualifierConsole
/login features/session/components/LoginForm
/signup features/session/components/SignupForm
404 features/not-found/NotFoundPage

Notable per-component changes

  • Pruned: /demo, /demo-tailwind (legacy showcase, no longer needed), the tw-components/components/ duplication (tw versions win where both existed), App.tsx, index.tsx, Router.tsx (Next App Router replaces them).
  • IconButton: iconUrl: stringIcon: 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 into useDismiss; removes ~25 lines of manual document.body event wiring.
  • TransitionWrapper: react-transition-group → mount/unmount. Visual fade is intentionally absent until PR2 reintroduces it via CSS Modules.
  • CookieBanner: cookie read deferred to useEffect after 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 from localStorage via useEffect, not at state init — SSR can't read localStorage.
  • Logo + credits illustration: SVGR-rendered SVGs need explicit h-{N} or h-auto to override their baked-in width/height attributes; CSS percentage widths alone don't propagate to height the way <img> does.
  • /privacypolicy/privacy-policy (hyphenated; standard URL casing).

SVG handling

  • @svgr/webpack configured in next.config.ts for both Webpack and Turbopack with svgo: false so SVGO's default cleanupIds doesn't break clip-path id references in the logo files.
  • Vite-style ?react/?url query suffixes are gone. Where the legacy used ?url for CSS background images (privacy-policy bg, landing page bg), the SVG file moved to public/svgs/ and is referenced as a path string — SVG-as-React-component cannot be used inside url(...).

Dev infra

  • dev/vite.Dockerfiledev/next.dockerfile.
  • docker-compose.yml: vite service → next service, port 5175 → 3000, develop.watch cleaned to ignore node_modules/ and .next/.
  • Docs (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 into stage/next.dockerfile + stage/django.dockerfile.
  • docker-compose.stage.yml runs three services: pgdb (5433:5432), django (8000), next (3000), with BACKEND_INTERNAL_URL=http://django:8000 driving Next's /api/* and /admin/* rewrites so the local stage matches the deployed stage's single-origin shape.
  • .github/workflows/build-deploy-stage.yml and aws/task-definition.json deleted — the deploy pipeline lives in hackforla/incubator now.

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

  • .gitignore rules data and media are now /data and /media. The unanchored versions matched any directory named data/media anywhere, which would silently hide the new per-feature data/ directories that the port introduces.

What's deferred

  • Tailwind classes + carry-over SCSS → CSS Modules (PR2 in this chain).
  • Disable GitHub Pages (post-merge, repo settings).
  • .github/workflows/lint.yml + delete legacy linter.yml (post-merge cleanup).
  • jest-react-test.yml workflow rename (still labeled "Jest" but runs Vitest now — functional but stale).
  • NEXT_PUBLIC_API_URL in dev/dev.env is cross-origin; with the rewrite-based stage shape, dev should probably use the same relative-URL pattern. Not addressed here.
  • SCSS deprecation warning in _NotFoundPage.scss (breakpoint-media-max("smtablet") style is deprecated). Pre-existing; warns but compiles.
  • TextField and LandingPage test rewrites (describe.skip with 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 on localhost:3000.
  • Walk the site:
    • / renders landing hero, mission section, CoP cards
    • Click a CoP card → dialog opens with that CoP's detail (X top-right, nav on left, content on right)
    • Footer "Donate" button opens hackforla.org/donate in a new tab
    • /credits toggles between Illustrations and Iconography; high-five illustration sized correctly
    • /privacy-policy renders with the bg-top illustration on lg screens
    • /login renders the form with side illustration; bad email shows validation error
    • /signup renders four fields + form-level validation
    • /qualifier/1 shows CoP cards; selecting one enables Continue
    • /qualifier/2 shows skill matrix; back/forward through skill batches works
    • /qualifier/3 shows weekly availability calendar; timezone dropdown opens and selects
    • /foo (any unknown URL) shows the 404 with HeaderNav + FooterNav
    • Cookie banner appears on first visit, dismisses, stays dismissed across reload (no hydration warning in console)
  • npm 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/* from localhost:3000 proxy 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 to develop and rebase. Squash-merge into develop is fine; the per-commit split (scaffold + port) is preserved here on the branch but does not need to land on develop as separate commits.

Nickatak and others added 4 commits April 26, 2026 16:16
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>
@Nickatak Nickatak marked this pull request as ready for review May 7, 2026 21:04
@RSkuma RSkuma self-requested a review May 7, 2026 21:50
Copy link
Copy Markdown
Member

@RSkuma RSkuma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed and approved

@RSkuma RSkuma merged commit 744ed74 into hackforla:develop May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants