Skip to content
<Dev.Lucas />
Back to the blog

Published on October 31, 2026 · 2 min read

How a single-page React app became readable to Google

The technical SEO approach behind Negócios: a sitemap generated at build time from API data, and every route prerendered in headless Chromium.

SEOReactVitePrerender

Negócios is a marketplace built as a single-page React app (SPA), with Vite and React Router. That is great for users, but a problem for anything that crawls the site: the HTML that arrives is almost empty and the content only shows up after JavaScript runs. Google does execute JavaScript, but more slowly and less reliably, and other crawlers and link previews (WhatsApp, LinkedIn) do not even try.

What had to be found

Demand in the marketplace arrives through searches like “electrician in Campinas” or “accountant in Curitiba”. So the site needs a page for each category and city combination, plus company profiles and blog articles, and each one has to deliver real text in the very first HTML.

Step 1: a sitemap built from real data

The indexable routes are not written in any file: they come from the API (categories, cities, profiles and posts). That is why the sitemap is generated at build time, querying the API and listing categories, category + city, profiles and the blog. A newly published company shows up in the sitemap without anyone editing a file.

Step 2: prerendering with a real browser

After the Vite build, an extra step opens each route in headless Chromium, waits for the page to finish rendering and saves the finished HTML. That saved HTML is what a crawler receives when it requests the route, already with real headings, text and links, even without running any JavaScript.

// Simplified illustration of the idea (not the project's code)
const browser = await chromium.launch();
const page = await browser.newPage();

for (const route of routes) {
  await page.goto(`${origin}${route}`, { waitUntil: "networkidle" });
  const html = await page.content();
  await writeFile(outputPathFor(route), html);
}

await browser.close();

Why prerendering instead of server-side rendering?

Moving to a server-rendering framework such as Next.js would be the most common answer, and for a new project it may be the best one. Negócios, however, is an SPA with several areas (customer, company, admin, chat) that is also packaged as an Android app with Capacitor. Prerendering the public routes delivers the SEO gain without rewriting the front end and without giving up the app.

Limits of this approach

  • The HTML is a snapshot from build time: new content only reaches the HTML after a new build (or an incremental prerender step).
  • Build time grows with the number of routes, since each one opens a page in the browser.
  • Private routes (dashboards and chat) are left out on purpose: they stay a plain SPA.

For content that changes throughout the day, regenerating only the affected routes is worth considering. The full build, on the other hand, is simple to operate and solves the core problem: delivering real text to whoever crawls the site.