USIN Academy: A Bilingual React Site, Prerendered for SEO

USIN Academy: A Bilingual React Site, Prerendered for SEO

Table of Contents

USIN Academy trains Indonesian creators to sell on Etsy and earn in US dollars. As a marketing site, its entire job is to be found and to convert — which makes it a poor fit for the default React single-page app, where crawlers and social scrapers receive an empty <div id="root"> and nothing else.

I built the site with Vite, React, and TypeScript, styled with Tailwind CSS and shadcn/ui — then added a build-time prerendering pipeline so every route ships real HTML.

The Challenge

A conversion-driven marketing site has requirements that fight against a client-rendered SPA:

  • Discoverability: Search engines and link-preview scrapers need per-page titles, descriptions, and Open Graph tags present in the initial HTML response — not injected after a JavaScript bundle executes.
  • Two audiences, two languages: The business serves an Indonesian audience writing about a US marketplace, so Indonesian is the default locale with English as a first-class alternative — not an afterthought.
  • Static hosting only: The site had to run on GitLab Pages with a custom domain. No Node runtime, no server-side rendering at request time.
  • Weight: A landing page dense with imagery, video, and carousels gets slow quickly, and slow marketing pages lose visitors before they read anything.

The Solution

1. Build-Time Prerendering

Rather than adopt a heavier meta-framework, I built a focused prerender step. Vite produces an SSR bundle from src/entry-server.tsx, and a Node script walks every route, renders it to HTML, and writes a static file:

  • Route discovery is derived, not hardcoded. prerender() returns the two static routes (/ and /en) and then enumerates blog posts per locale, so adding a Markdown article automatically produces a prerendered page. The site currently emits 19 static routes.
  • Real <head> content per page. After each render, react-helmet’s static output is extracted and injected into the template, so every route ships its own title, meta description, and social tags.
  • No runtime cost. The output is plain HTML that hydrates into the full React app on load, so the result is a static site that behaves like an SPA once it’s running.

2. Bilingual Architecture

Indonesian is the default locale, with English served from /en:

  • A runtime i18n provider backed by id.json and en.json translation catalogs.
  • A parallel Markdown article library per language, rendered through marked, with locale-aware routes (/blog/:slug and /en/blog/:slug).
  • Because prerendering enumerates both catalogs, each language’s articles are independently indexable.

3. Performance as a Build Step

Instead of relying on discipline, optimization is enforced by the pipeline:

  • Automatic WebP conversion: a sharp-based script converts raster assets after the build and reports the savings.
  • HTML rewriting: a follow-up script rewrites image references and preload hints to prefer WebP while preserving fallbacks, so no markup has to be maintained by hand.
  • Deferred service worker: the PWA plugin’s automatic registration is disabled so the worker registers on idle instead of competing with first paint.
  • Lazy YouTube embeds: a lightweight facade component replaces the standard iframe, so the video player only loads on interaction.
  • Visibility-gated sections: below-the-fold blocks render only when scrolled into view.

4. Deployment

  • GitLab Pages as the primary target, with the custom domain wired in through CI.
  • A GitHub Actions workflow mirrors the same build, so either host can serve the site.
  • A configurable BASE_PATH lets the same codebase deploy to a domain root or a project subpath without hand-edited asset URLs.
  • An SPA-aware 404.html fallback keeps deep links working on static hosts.

Technologies Used

  • Framework: React 18, TypeScript, Vite
  • UI: Tailwind CSS, shadcn/ui (Radix primitives), Embla and Swiper carousels
  • Rendering: Vite SSR build plus a custom prerender script, react-helmet
  • Content: Markdown articles rendered with marked
  • Forms: React Hook Form with Zod validation and reCAPTCHA v3
  • Optimization: sharp, vite-plugin-pwa, Lighthouse in the toolchain
  • Deployment: GitLab CI/CD and GitHub Actions to static Pages hosting

Outcome

  • Every route is crawlable. All 19 pages serve complete HTML with their own metadata, with no JavaScript execution required to read them.
  • Content scales without configuration. A new Markdown file becomes a prerendered, indexable page on the next build.
  • One codebase, two locales, each fully prerendered rather than switched at runtime.
  • Modern formats by default. Images ship as WebP with fallbacks, handled by the build rather than by hand.
  • No server to operate. The whole site is static files behind a CDN.

This project is a good illustration of choosing a targeted solution over a heavier one: a single prerender script delivered the SEO characteristics the business needed without migrating the app to a different framework.

Feel free to visit the live site or get in touch if you’d like to discuss a similar build.

call to action

Ready to build your next project with me?

I’m ready to help you build, improve, and launch your next project — just drop a message and let’s get started.

Get Started Now