#23 — React Migration Foundation V1 - #23
Conversation
Installs React 19, React DOM, React Router 7, Vite 8 and @vitejs/plugin-react as a parallel build system. Nothing in the live site changes: Vite is rooted at src/react/, so the repository root stays a plain static site with no Vite entry, and the build writes only to dist-react/, which is git-ignored so GitHub Pages cannot publish it. The pre-render script is the reason this is not a client-only SPA. It builds the client bundle, compiles a server entry, renders each route with renderToString and writes real HTML per route. It fails the build if a route renders under 1 KB of markup, so a silently empty render cannot ship. vite.config.mjs uses the .mjs extension deliberately: qa-js-syntax.js parses every root-level .js file as a classic script, and an ESM config would fail that check for no useful reason. The pre-render script lives in scripts/ for the same reason. The preview server is configured to behave like a static host rather than an SPA server -- exact file, directory index, or 404.html with a real 404 status. Vite's SPA fallback would make client-side routing look like it works on GitHub Pages when it does not, and would serve the home page's pre-rendered markup at a URL whose client render is NotFound, which is a genuine hydration mismatch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three preview routes under /react-preview/, driven by React Router and pre-rendered to real HTML: an overview, an approach page and a NotFound view. None of them is reachable from any public surface -- they are noindex, unlinked, and absent from sitemap.xml, navigation, Recruiter Mode and Ajoop. Header and footer render the canonical profile truth rather than re-typed copy: Forward Deployed Engineer as the primary title, AI Designer & Software Developer as the background, and exactly the five canonical social destinations. The values live in a temporary parity fixture because portfolio-data.js is a browser global that a Vite module graph cannot import; converting the registry to JSON is #24, and until then a guard fails the build if the fixture drifts. Theme and language reuse the production storage keys, so a visitor's choice survives moving between the legacy site and the preview. Both start at their server-rendered defaults and reconcile in an effect after hydration, which is what keeps the first client render identical to the pre-rendered markup. The blocking inline theme script in the document head applies the stored theme before first paint, exactly as the production pages do, so correct colors do not wait for that effect. Result is 0 hydration errors and 0 React warnings, including on a reload with non-default preferences stored. The 404 view deliberately does not echo the attempted path: one pre-rendered file is served for arbitrary unknown URLs, so echoing it would guarantee a mismatch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
qa-react-foundation.js proves the two properties that matter about this pass. First, that the build really pre-renders. It fails if a route ships an empty root container, if less than 1 KB was rendered into it, if a route heading is missing, or if the semantic landmarks did not survive the build. Both failure paths were exercised deliberately and confirmed to exit non-zero. Second, that production is untouched. It checks that every production page still boots script.js and references neither the build output nor the preview, that the preview stays out of sitemap.xml and robots.txt, that dist-react is git-ignored, and that the Vite config still points away from the site root. It also loads the real registry and compares it against both the parity fixture and the rendered output, so the fixture cannot become a second source of truth. Those comparisons are field-scoped rather than whole-file: the footer tagline contains the primary title, so a substring search over the document would pass even with an invented job title in the header. CI gains three blocking steps. The React build is also the JSX gate, since qa:js parses root files as classic scripts and cannot represent JSX. The preview gets its own Pa11y config so it adds accessibility coverage rather than displacing any; the guard asserts production Pa11y and Lighthouse still cover all 11 pages. No existing gate was weakened, and Lighthouse remains report-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
REACT_MIGRATION_PLAN.md is the contract for #23 through #33: the target stack, why React and Vite rather than a meta-framework, why JSX before TypeScript, the phase list, the parity rules a page must satisfy before it is migrated, and the rule that legacy is removed only after parity is proven and only in a separate pull request. Two findings from this pass are written down because they constrain every later phase. Production URLs are flat .html files at the site root and must not change, so migrated routes emit works.html rather than works/index.html. And GitHub Pages offers no rewrite rules or SPA fallback, so pre-rendering each route to its own file is what makes a route directly reachable at all -- not an optimization. The Vite dev-server fallback is explicitly called out as not being evidence about production hosting. PORTFOLIO_ARCHITECTURE.md gains the transitional section describing the two architectures now in the repository and the boundary between them. QA_BASELINE.md records measured numbers: build output with gzip sizes, per-route pre-rendered markup, routing status codes, and contrast minimums in both themes. Two contrast failures found during this pass were fixed rather than accepted. The Build Log entry says a foundation was established beside the existing site. It does not say the site was migrated to React, because it was not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The migration plan had invented its own phase order instead of using the locked master roadmap agreed with the owner. Replaced with the real structure: #24 shared shell + JSON data foundation, #25 Home + About, #26 Works + Games, #27 Recruiter Mode + Build Log, #28 Ajoop + Command Palette, #29 case studies + dynamic project routes, #30 Labs + mini-game shells, #31 legacy runtime removal, #32 external dependency + bundle cleanup, #33 hardening and V3 final. Section 4 is now marked authoritative, so a later implementation pass cannot quietly redesign it again. The real roadmap is also better than the one I invented on a point that matters to the legacy-removal policy: every migration phase runs before the first removal phase, and removal is concentrated in #31 once the migration matrix reaches zero legacy usage, rather than being spread across phases. Sections 9 and 10 and the architecture doc now reflect that, and #30 records that gameplay may remain vanilla JS/canvas. Also removed the claim that TypeScript is "the right long-term answer" and scheduled after the JSON migration. That decision was not made. JavaScript/JSX is the intentional language of V3, TypeScript is outside its scope, and any reevaluation after #33 is the owner's call. Turkish copy cleanup in the Build Log entry and the preview i18n proof: "tüm herkese açık sayfalar" -> "herkese açık tüm sayfalar". No React implementation change beyond that one translated string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review fix — locked roadmap restoredHead: 1. Locked #23–#33 roadmap restored. The real roadmap is better than the one I invented on a point that matters to §9: every migration phase (#24–#30) runs before the first removal phase (#31), and removal is concentrated there once the migration matrix reaches zero legacy usage — rather than being spread across phases as I had it. §9, §10 and 2. TypeScript commitment removed. The claim that TypeScript is "the right long-term answer" and scheduled after the JSON migration is gone. §3 now states JavaScript/JSX is the intentional language of V3, TypeScript is outside V3 scope, no adoption date is promised, and any reevaluation after #33 is the owner's call. 3. Architecture findings preserved — static/pre-render requirement, GitHub Pages route constraints, flat production URLs unchanged, per-route configurable output, hydration parity rule, production-before-legacy-removal, mini-game gameplay stays vanilla, rollback principle, QA additive-never-weaker. 4. Docs aligned. Every phase reference across 5. Turkish copy. QA on
|
| Check | Result |
|---|---|
npm ci |
528 packages, reproducible |
npm run qa |
pass — 18/18 JS, 0 HTML errors, 0 spelling |
npm run build:react |
pass — 3 routes pre-rendered |
npm run qa:react |
pass |
npm run qa:a11y |
11/11, 0 errors |
npm run qa:a11y:react |
3/3, 0 errors |
git diff --check |
clean |
TR preview copy verified rendering in-browser with 0 console output.
No production truth or product facts changed. DO NOT MERGE.
#23 — React Migration Foundation V1
Installs and proves the React architecture beside the existing production site. No public page is replaced, and no legacy code is removed.
What this is
React 19 + Vite 8 + React Router 7 running as a parallel architecture, with three pre-rendered preview routes under
/react-preview/. The live site is byte-identical apart from one Build Log entry.This is not a migration. Nothing on
kaanbalci.comis served by React.Production isolation
src/react/— the repository root is not a Vite project and has no Vite entrydist-react/, git-ignored, so GitHub Pages cannot publish it/react-preview/, never a production routenoindex, nofollow; absent fromsitemap.xml, navigation, Recruiter Mode and AjoopDiff against
maintouches zero production HTML, CSS, or runtime JS. The only production-visible edit is a 7-line Build Log entry inportfolio-data.js.Pre-render proof
Every route contains real content before any JavaScript runs — an empty
<div id="root"></div>is explicitly treated as failure:/react-preview//react-preview/about404.htmlEach carries its own
<title>,description,robots noindex, andheader/nav/main/footerlandmarks. The build fails under 1 KB of markup; the guard fails on an empty root. Both failure paths were exercised and confirmed to exit non-zero.Hydration
0 errors, 0 React warnings across every route — including a reload with non-default preferences (TR + light) stored, and
404.htmlserved at an arbitrary unknown URL.Theme and language start at their server-rendered defaults and reconcile after hydration, so the first client render matches the pre-rendered markup exactly. The blocking inline theme script applies the stored theme before first paint, as the production pages do.
Routing, and what it says about hosting
/react-preview//react-preview/about/react-preview/does-not-existClient navigation, Back and Forward were each confirmed to be real client transitions (a
windowvalue survived them).The preview server is deliberately configured not to use an SPA fallback. GitHub Pages has no rewrite rules, so a route is directly reachable only if a file exists for it.
vite devdoes fall back — that is a development convenience and is not evidence about production. This distinction drove the conclusion in the plan that migrated routes must emitworks.html, notworks/index.html, so existing canonical URLs survive.QA
npm run qanpm run build:reactnpm run qa:reactnpm run qa:a11y(production, 11 pages)npm run qa:a11y:react(preview, 3 routes)Three new blocking CI steps. No existing gate weakened; Lighthouse and the external link scan remain the only report-only checks. Production Pa11y and Lighthouse still cover the same 11 pages — the preview has its own config so it adds coverage rather than displacing any, and the guard asserts this.
Bundle
254.5 KB raw / 81.6 KB gzip total; 1 JS chunk, 1 CSS chunk, 3 HTML files. Essentially React + React Router. Not comparable to the legacy production bundle, and not production cost today.
Truth guards
qa-react-foundation.jsloads the real registry and fails on drift in the primary title, background title, footer positioning copy, or any of the five canonical social URLs. The comparisons are field-scoped, because the footer tagline contains the primary title and a whole-document search would miss an invented job title in the header — verified by deliberately introducing one.Forward Deployed EngineerandAI Designer & Software Developerare unchanged.Two fixes made along the way
Contrast failures found while testing the preview, fixed rather than accepted:
--brandon light: 3.94:1Minimums now 4.61:1 light / 6.06:1 dark.
Documentation
REACT_MIGRATION_PLAN.md(new) — target stack, rationale, the locked #23 — React Migration Foundation V1 #23–#33 master roadmap (§4, authoritative), parity rules, pre-render and route policy, data and legacy-removal policy, mini-game strategy, QA strategy, rollback principlePORTFOLIO_ARCHITECTURE.md— transitional architecture sectionREADME.md,SITE_PREFLIGHT.md,QA_BASELINE.mdupdated with measured numbersDeferred (explicitly not in this PR)
Everything in phases #24–#33 of the locked roadmap: shared shell + JSON data foundation (#24) · Home + About (#25) · Works + Games (#26) · Recruiter Mode + Build Log (#27) · Ajoop + Command Palette (#28) · case studies + dynamic project routes (#29) · Labs + mini-game shells (#30) · legacy runtime removal (#31) · external dependency + bundle cleanup (#32) · hardening + V3 final (#33).
TypeScript is not scheduled in any V3 phase.
🤖 Generated with Claude Code