Guide Updated

How do I make a Vite + React app readable by Google and AI bots?

Short answer

Vite isn’t bad for SEO, but its default React template ships an empty div and fills it in with JavaScript. Prerender your public routes at build time with React Router’s prerender option, vite-react-ssg, TanStack Start or Vike, give each route its own title, and make unknown URLs return a real 404. Then curl each route as a bot: the text should be in the HTML before any script runs.

What Vite gives you by default

A fresh npm create vite@latest -- --template react-ts project builds one index.html with <div id="root"></div> and a script tag. Every route serves that file, and React draws the page in the browser. Googlebot renders it later from a queue; the crawlers behind ChatGPT and Claude did not run JavaScript in Vercel and MERJ’s 2024 log study, so they see the empty div. The background is on Is React bad for SEO? and Do AI crawlers render JavaScript?.

Vite itself supports prerendering: its SSR guide says you can “pre-render these routes into static HTML using the same logic as production SSR”, but it ships a demo script, not a plugin (Vite, “Server-Side Rendering”, v8.3.4, read 15 Oct 2026). In practice you use a framework or plugin on top.

Four ways to prerender (versions checked on npm, 15 Oct 2026)

Option Version Fits when Effort
React Router framework mode, prerender react-router 8.4.0 You already use React Router Medium: move to framework mode (Vite plugin, routes.ts)
vite-react-ssg 0.9.2 (15 Jul 2026) React Router v6 data routes Low: new entry and build command
TanStack Start, prerender @tanstack/react-start 1.169.2 New project, or a Lovable app upgraded to TanStack Start Medium
Vike, prerender: true vike 0.4.267 You want control over the server and build Medium

React Router

// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  ssr: false,                      // no runtime server, static files only
  async prerender({ getStaticPaths }) {
    const slugs = await getPostSlugs();
    return [...getStaticPaths(), ...slugs.map((s) => `/blog/${s}`)];
  },
} satisfies Config;

prerender: true covers static paths only; dynamic segments like /blog/:slug need the list. The build writes build/client/<path>/index.html per route, plus a __spa-fallback.html for the rest when / is prerendered (React Router, “Pre-Rendering”, read 15 Oct 2026).

vite-react-ssg

// src/main.tsx  (package.json: "build": "vite-react-ssg build")
import { ViteReactSSG } from "vite-react-ssg";
import { routes } from "./App";

export const createRoot = ViteReactSSG({ routes });

Dynamic routes add getStaticPaths: () => ["nest/b1", "nest/b2"]. Its README recommends React Router’s built-in prerendering for v7 and later and keeps itself for v6 users (npm README, read 15 Oct 2026).

TanStack Start and Vike

// vite.config.ts (TanStack Start)
import { defineConfig } from "vite";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [tanstackStart({ prerender: { enabled: true, crawlLinks: true } }), viteReact()],
});

With crawlLinks, every page linked from a prerendered page is rendered too (TanStack Start, “Static prerendering”, read 15 Oct 2026). In Vike, export default { prerender: true } in pages/+config.js writes the HTML to dist/client/ (Vike, “Pre-rendering”, read 15 Oct 2026).

Five steps, whichever tool you pick

  1. List the public routes. Home, pricing, features, docs, blog, situation pages. Leave /app/* client-rendered.
  2. Put the title and description in each route. With React 19 you can render <title> and <meta name="description"> inside the component; React Router’s route docs now recommend that over its meta export. Prerendering puts them into each HTML file.
  3. Fix the host’s fallback. A rewrite of /* to index.html with a 200, or Cloudflare’s not_found_handling: "single-page-application", answers every unknown path with your app. Serve prerendered files first and a real 404 page for the rest.
  4. Use real links. <Link to> renders an <a href>; onClick={() => navigate(...)} on a div doesn’t, and crawlers follow only real links.
  5. Verify every route as a bot. See the loop below.

Check every route (example)

UA='Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; GPTBot/1.4; +https://openai.com/gptbot'
for p in / /pricing /features /blog/first-post /does-not-exist; do
  printf "%-20s %s %5s words  %s\n" "$p" \
    "$(curl -s -o /dev/null -w '%{http_code}' -A "$UA" "https://yoursite.com$p")" \
    "$(curl -s -A "$UA" "https://yoursite.com$p" | sed 's/<[^>]*>/ /g' | wc -w)" \
    "$(curl -s -A "$UA" "https://yoursite.com$p" | grep -o '<title>[^<]*' | head -1)"
done

Pass: every real route returns 200 with hundreds of words and its own title; /does-not-exist returns 404. If all routes share one title, step 2 didn’t make it into the build. The user agent string is the one OpenAI lists today (GPTBot/1.4, OpenAI crawlers, read 15 Oct 2026). If bots get 403 instead, the block is at your CDN: see Cloudflare blocking AI crawlers.

Where Katman fits, and where it doesn’t

Katman is an MCP server inside your AI coding tool (Cursor, Claude Code, VS Code, Windsurf and others). katman_scan_code detects Vite, the router and whether each page renders only in the browser; katman_prompt returns the rendering fix prompt with prerender options for a Vite single-page app and an acceptance check; katman_audit_site re-fetches the live routes as AI crawlers do and confirms the text, titles and 404s. Your coding tool writes the code; Katman doesn’t touch the repo by itself. Katman Pro is $99 a month for up to 3 sites, with fix prompts and page briefs uncapped (pricing). It can’t make an assistant recommend you; it makes sure the pages can be read and checks each fix. The step order is in the Katman method.

Written by the Katman team, Rnify Limited. Vite, React Router, TanStack, Vike, OpenAI and npm pages read 15 October 2026.

Who this is not for

  • Vite apps that are only a logged-in dashboard: prerendering adds nothing there.
  • Vue or Svelte projects on Vite: the idea is the same, the plugins differ.
  • Sites already on Next.js or Astro: see those guides instead.

Frequently asked questions

Is Vite bad for SEO?

No. Vite is a build tool; the SEO problem is the client-only single-page app its React template starts with. Add prerendering or server rendering and Vite output is plain HTML like any other site.

Which prerender option should I pick for an existing Vite SPA?

If you already use React Router, its prerender option is the shortest path, after moving to framework mode. On React Router v6 data routes, vite-react-ssg fits. New projects can start on TanStack Start or Vike.

Is react-snap still an option?

We wouldn’t start with it today: its latest npm release is from December 2018 (registry, read 15 Oct 2026). The maintained options in the table cover the same job.

Vite vs Next.js for SEO: which is better?

Both can ship complete HTML. A prerendered Vite app and a statically generated Next.js page look the same to a crawler; pick Next.js when you need per-request server rendering, not for SEO alone.

My Lovable app is React + Vite. Do I need any of this?

Check first. Lovable says older React + Vite apps get prerendered HTML for verified crawlers on its hosting, and apps created from 13 May 2026 use TanStack Start with server rendering. Self-hosted exports don’t get Lovable’s prerendering.

Why do prerendered pages still show up as soft 404s?

Usually the host’s SPA fallback. A rewrite that answers every unknown path with index.html and a 200 turns typos and deleted pages into empty pages. Serve a 404 page with a 404 status instead.