feat: prerender 624 multilingual blog articles + recharts chunk split#20
Merged
Merged
Conversation
- prerender.mjs: concurrency pool (4 parallel Chrome tabs) renders all 104 blog slugs × 6 languages = 624 routes, producing static dist/blog/<slug>/index.html and dist/<lang>/blog/<slug>/index.html so GitHub Pages serves HTTP 200 instead of the 404-redirect for crawlers - generate-sitemap.mjs: re-enable blog slug URLs now that static files exist; sitemap grows from 8 to 112 <loc> entries with full hreflang - vite.config.js: isolate recharts + d3/victory deps into vendor-recharts chunk (74 KiB gz); vendor chunk drops from 138 → 62 KiB gz; recharts is never on the critical path (all consumers are lazy-loaded) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
miquelmatoses
added a commit
that referenced
this pull request
May 16, 2026
…, optimize images (#21) Production audit caught three remaining issues: 1. /blog pre-rendered HTML in production contains the 'Could not load articles' error fallback. Google Search Console rejects indexing with 'Soft 404'. Root cause: during the PR #20 CI build, BlogIndexPage's API call during prerender failed (likely due to the 4-page concurrent pool hammering api.cercol.team with too many simultaneous requests). Puppeteer captured the error state. Commit 131c986 (waitForFunction guard) was on main but never deployed (scripts/ missing from deploy path filter), AND even if deployed wouldn't help in this case — if the API stays failed during build, the guard times out and the error HTML is captured anyway. 2. 91 of 104 blog cover image URLs in the database have malformed double-? query strings, causing Unsplash to serve 3840px+ images at 380x214 display. The blog slug LCP is dominated by this: 722 KiB cover image, LCP 9.1s. 3. BetaBanner causes 1300ms LCP delay on the landing page because useState(null) unmounts the pre-rendered banner during React hydration; the API roundtrip then re-mounts it 1300ms later. The unified solution: stop depending on API calls at render time for pre-rendered routes. The prerender script fetches the article list and beta status ONCE and injects them as window globals into every pre-rendered HTML. React reads from window synchronously, eliminating the hydration flicker and the API failure mode. Changes: deploy-frontend.yml: add scripts/** to path filter so prerender changes trigger fresh deploys. src/utils/unsplash.js (new): normalizeUnsplashUrl() fixes the double-? URLs and applies w/q/fit overrides. 5 unit tests. BlogArticlePage.jsx: cover uses normalizeUnsplashUrl(coverUrl, {w:760}) + fetchpriority='high' + loading='eager' + explicit width/height. BlogIndexPage.jsx: - useState reads from window.__BLOG_ARTICLES__ on first render - useEffect still refreshes from API for stale-data resilience - Error state only shown when we have NO articles from ANY source - Thumbnails use normalizeUnsplashUrl(coverUrl, {w:400}) + lazy BetaBanner.jsx: useState reads from window.__BETA__ on first render. scripts/prerender.mjs: - fetchBlogArticles() called once at start (throws on failure to fail the build loudly rather than silently shipping broken HTML) - fetchBetaStatus() called once at start (silent fallback to {remaining:500,total:500,active:true}) - Both values injected as <script>window.__BETA__=...; window.__BLOG_ARTICLES__=...;</script> in <head> of every HTML Expected outcomes: - /blog and 5 language variants: HTML contains 104 article cards, no 'Could not load' fallback. Google Search Console accepts indexing. - Blog slug LCP: 9.1s -> ~2.5s - Blog slug Performance: 66 -> ~85-90 - Landing LCP: 4.4s -> ~2.5-3.0s - Landing Performance: 76 -> ~82-86 Co-authored-by: miquelmatoses <miquelmatoses@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Summary
scripts/prerender.mjsnow generates a staticdist/blog/<slug>/index.html(anddist/<lang>/blog/<slug>/index.htmlfor CA/ES/FR/DE/DA) for all 104 blog slugs, so GitHub Pages serves HTTP 200 instead of the 404 SPA-redirect that Googlebot was seeing. A concurrency pool of 4 parallel Chrome tabs keeps CI build time ~3–4 min instead of 50+ min for sequential execution.generate-sitemap.mjsun-comments the blog slug block (disabled in Phase 15 because of 404s). Sitemap grows from 8 → 112<loc>entries, all with full hreflang alternates.vite.config.jsisolates recharts + its d3/victory/internmap deps into a dedicatedvendor-rechartschunk (74 KiB gz). Thevendorchunk drops from 138 → 62 KiB gz. recharts is never on the critical path for/(all consumers are lazy-loaded report/role pages).Verification
probe-meta.mjs(temporary, deleted) confirmed puppeteer captures article-specific<title>,<meta description>, and<link rel="canonical">atnetworkidle0 + 1500ms— nowaitForFunction()guard needed.Test plan
curl -s -o /dev/null -w "%{http_code}" https://cercol.team/blog/big-five-personality-across-cultures-what-research-showsreturns200https://cercol.team/ca/blog/big-five-personality-across-cultures-what-research-showshttps://cercol.team/sitemap.xmlcontains blog article URLsvendor-recharts-*.jspresent indist/assets/and not loaded on homepage network tab🤖 Generated with Claude Code