MBS‑Digitals logo
Home
AboutResourcesSupport
Get in touch →
MBS‑Digitals logo

Empowering Businesses Through the Web

Stay up to date
Products
  • All products
  • Web Applications
  • Shopify Apps
Resources
  • Blog
  • Resources
  • Support
  • contact@mbs-digitals.com
Company
  • About
  • Contact
  • Privacy
  • Terms
© 2026 MBS‑Digitals. All rights reserved.
Web development

Your React + Vite Site Is Invisible to Google. Here’s How to Fix It

Mahmoud Bensaida · October 14, 2026 · 8 min read

A React + Vite single-page app is a great developer experience. But open “View source” on your production site and you’ll probably see something like this:

<body>
  <div id="root"></div>
  <script type="module" src="/assets/index.js"></script>
</body>

That’s what many crawlers and every social-media link preview see. Google can render JavaScript, but it does so later and with less priority. Facebook, LinkedIn, X, Slack and WhatsApp previews don’t run your JavaScript at all, so every page shares the same generic title and description from index.html.

I fixed this on our own site without moving to Next.js. Here’s the approach.

Step 1: Make your head tags per-route

Each route needs its own <title>, meta description, canonical URL and Open Graph tags. With React 19 you can render <title> and <meta> inside components and React hoists them into <head>. Libraries like react-helmet-async build on that. Wrap it in one SEO component so every page passes a title and description, and let the component work out the canonical URL from the current path.

Step 2: Prerender every route at build time

The idea is simple: after the normal client build, render each route to an HTML string on the server and write it to dist/<route>/index.html. Static hosts like Vercel and Netlify serve that file directly, and your JavaScript takes over in the browser as before.

First, a server entry that renders the app for a given URL using StaticRouter:

// src/entry-server.tsx
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom'
import App from './App'

export function render(url: string) {
  return renderToString(
    <StaticRouter location={url}>
      <App />
    </StaticRouter>
  )
}

Then a small Node script that runs after the build:

// scripts/prerender.mjs (simplified)
const template = fs.readFileSync('dist/index.html', 'utf8')
const { render } = await import('../dist-ssr/entry-server.js')

for (const route of routes) {
  const html = render(route)
  const page = template.replace('<div id="root"></div>',
                                `<div id="root">${html}</div>`)
  fs.mkdirSync(`dist${route}`, { recursive: true })
  fs.writeFileSync(`dist${route}/index.html`, page)
}

And wire it into your build script:

"build": "tsc && vite build && vite build --ssr src/entry-server.tsx --outDir dist-ssr && node scripts/prerender.mjs"

With React 19, the hoisted <title> and <meta> tags come out at the start of the rendered string. Move them into the template’s <head> and remove the default title and description so each page has exactly one of each.

Step 3: Watch for code that only runs in the browser

Server rendering fails if components touch window, document or localStorage while rendering. Keep that code inside useEffect or event handlers, which don’t run on the server. Animation libraries like Framer Motion handle this fine.

Step 4: Get canonicals and the sitemap right

The most damaging SEO bugs I find in SPAs aren’t about rendering at all:

  • Canonical URLs pointing at the wrong domain. A typo, an old domain or a missing hyphen tells Google the “real” page lives somewhere else. Keep the site URL in one config constant and build every absolute URL from it.
  • Hand-written sitemaps that drift. Generate sitemap.xml in the same prerender script from the same route list, so it can’t fall out of date.
  • 404s returning indexable pages. SPA hosts return HTTP 200 for unknown URLs. Add noindex to your not-found page.
  • Structured data that doesn’t match the page. Ratings or prices in JSON-LD must match what users actually see. Made-up ratings can earn a manual penalty.
  • Pages without an H1. Section components that always render <h2> leave whole pages without a main heading. Give the component an as prop.

Step 5: Verify

  • Run curl https://yoursite.com/some-page and confirm the content and meta tags are in the raw HTML.
  • Load the page in a browser and check there’s exactly one title, description and canonical tag.
  • Paste a URL into a social-media post composer and check the preview card.
  • Submit the sitemap in Google Search Console and use URL Inspection on key pages.

When to use a framework instead

Build-time prerendering is ideal for marketing sites, docs and blogs, where content changes when you deploy. If you need per-request data (logged-in pages, live inventory, thousands of dynamic URLs), a framework with server rendering such as Next.js, Remix/React Router framework mode or Astro is the better long-term fit.

Need this done on your site? I build and fix React, Node.js and Shopify projects, including SEO rescues like this one. Tell me about your project.

More web development articles

  • Building a Shopify App in 2026: 8 Lessons I Learned the Hard Way
  • Core Web Vitals in Practice: A Checklist for Faster Websites and Online Stores
Back to Blog