Case Study

← Back to selected work

Build-Time Prerendering for a Legacy CRA Homepage

Improving the Theta EdgeCloud homepage's crawlability and Lighthouse performance without migrating the whole app to Next.js or adding a runtime SSR server.

Summary

Role

Frontend architecture and implementation

Scope

Legacy CRA homepage, build pipeline, SEO metadata, Nginx routing, Lighthouse follow-up

Focus

Crawlability, prerender boundary, SPA preservation, web performance

Outcome

LCP improved from 20.4s to 1.9s; SEO stayed 100

TL;DR

Instead of treating SEO as a reason to rewrite the app, I drew a clean boundary: prerender the public homepage, preserve the authenticated dashboard, and keep the migration risk low.

Problem

Before

Mostly empty raw HTML

Empty CRA shellJavaScript required for contentMetadata not homepage-specific

Needed

Public homepage visible without a rewrite

Crawlable landing contentHomepage SEO metadataDashboard unchanged

The legacy CRA homepage depended on JavaScript for product content and links. Crawlers, social preview bots, and non-JS clients needed a useful public shell, but a full framework migration was more risk than the problem required.

Decision

Build-time prerendering Exact / only

Build lane

CRA buildhashed JS/CSS
Node scriptinject SEO shell
landing.htmlcrawlable content

Routing lane

Homepage exact routeStatic SEO shell
Dashboard / auth / app routesCRA fallback

The boundary preserved the existing dashboard SPA, Docker/Nginx deployment, proxying, ports, and Kubernetes assumptions.

Implementation

Build-time SEO shell

After CRA produced the app shell and hashed assets, a Node script injected homepage-specific metadata and crawlable landing markup into a generated landing.html file.

Exact-route boundary

Nginx served landing.html only for exact /. Dashboard, auth, and app routes continued using the existing CRA index.html fallback.

Progressive pricing data

Static fallback pricing made raw HTML useful immediately, while client-side hooks enhanced GPU cards and AI inference pricing with live marketplace data.

Shared formatting utilities

Reusable GPU formatting, filtering, and on-demand pricing parsing kept landing and dashboard logic aligned where the domains overlapped.

Performance Pass

Moved the decorative landing background image out of the DOM and into CSS after Lighthouse selected it as LCP.

Tightened image loading with explicit dimensions, async decoding, smaller product images, lazy loading for non-critical media, and eager loading for initial product images.

Deferred analytics, preconnected/preloaded fonts, and scheduled landing pricing fetches during idle time.

Results

LCP

20.4s → 1.9s

Accessibility

84 → 100

Best Practices

77 → 100

SEO

100 → 100

Validation

Rollout checks

I validated the rollout by comparing raw HTML between production and beta, checking that exact / contained prerendered landing content while dashboard routes stayed on the CRA fallback, and confirming assets, CTA links, and SEO metadata behaved correctly.

Trade-offs

  • This was not full SSR; it was a static shell for the public homepage.
  • It did not remove the legacy CRA bundle cost. React still loaded the existing app and took over client-side.
  • Remaining TBT was mostly from the legacy CRA main bundle and app initialization.
  • The next step would be route-level code splitting or separating the public landing bundle from authenticated dashboard code.