
USIN Academy: A Bilingual React Site, Prerendered for SEO
- Ibrahim Fiqhan
- Website , Frontend
- November 13, 2025
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.jsonanden.jsontranslation catalogs. - A parallel Markdown article library per language, rendered through
marked, with locale-aware routes (/blog/:slugand/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_PATHlets the same codebase deploy to a domain root or a project subpath without hand-edited asset URLs. - An SPA-aware
404.htmlfallback 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.

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




