🏠 Home 🚀 Get Started ⚙️ Configuration 🔌 API Reference 🧩 Middleware 💡 Examples 🐙 GitHub ↗

ReviJs

Local-first SPA prerender CLI — convert your React/Vite app into SEO-friendly static HTML. Zero cloud. Zero backend.

npm ↗ GitHub ↗
# install
npm install @revijs/core

# run
npx revijs

✔ / rendered
✔ /about rendered
3 routes → dist-prerendered/
0
Cloud dependency
30s
Setup time
30+
Bots detected
MIT
License — free

What is ReviJs?

React apps are invisible to search engines because they render HTML using JavaScript — and most bots don't run JavaScript. ReviJs fixes this by opening your app in a real headless browser, waiting for everything to load, and saving the final HTML to disk.

That saved HTML gets served to bots like Googlebot, GPTBot, and ClaudeBot — while real users continue to get your normal React app. No code changes needed in your app.

How it works

  1. You run npm run build as normal
  2. You run npx revijs
  3. ReviJs reads your revi.config.js
  4. For each route — starts a local server, launches headless browser, navigates, waits for network idle, captures full DOM
  5. Saves each route as a static index.html file in dist-prerendered/
  6. You deploy — bots get static HTML, users get the SPA

Features

🖥️ Headless Browser Engine
Powered by Playwright Chromium internally. You never install or configure it manually.
🔒 Local-First
Everything runs on your machine. No data leaves your server.
🤖 Bot Middleware
Optional Express middleware auto-detects bots and serves prerendered HTML.
⚙️ 3 Render Engines
browser, advanced, and ssr modes for different needs.
🔌 Programmatic API
Use prerender() in your own build scripts.
📦 Zero Config
Run npx revijs init to generate a starter config instantly.

Getting Started

Set up ReviJs in your existing project in under 30 seconds.

Requirements

Step 1 — Install

Install ReviJs. Playwright Chromium installs automatically — nothing extra needed.

npm install @revijs/core
💡 The browser engine (Chromium) installs automatically via postinstall. You never need to run playwright install manually.

Step 2 — Create config

Auto-generate a config file:

npx revijs init

Or create revi.config.js manually:

export default {
  routes: ['/', '/about', '/blog/post-1'],
  engine: 'browser',
  outputDir: 'dist-prerendered',
  distDir: 'dist',
  waitFor: 1200,
  headless: true,
};

Step 3 — Build your app

npm run build

Step 4 — Prerender

npx revijs

Expected output:

  ⚡ ReviJs — SPA Prerenderer

  Rendering / ... ✔
  Rendering /about ... ✔
  Rendering /blog/post-1 ... ✔

  3 rendered → dist-prerendered/
⚠️ Always run npm run build before npx revijs. ReviJs renders your built output, not your source files.

Output structure

Each route maps to a nested index.html:

/             → dist-prerendered/index.html
/about        → dist-prerendered/about/index.html
/blog/post-1  → dist-prerendered/blog/post-1/index.html

Configuration

All options for revi.config.js. Run npx revijs init to generate one automatically.

Full example

export default {
  routes: ['/', '/about', '/blog/post-1'],
  engine: 'browser',
  outputDir: 'dist-prerendered',
  distDir: 'dist',
  waitFor: 1200,
  headless: true,
  port: 4173,
  debug: false,
};

Options

OptionTypeDefaultDescription
routesstring[][]Route paths to prerender. Required.
enginestring'browser'Rendering engine. See below.
outputDirstring'dist-prerendered'Where to write prerendered HTML files.
distDirstring'dist'Your built SPA directory. Must exist before running.
waitFornumber1200ms to wait after network idle. Increase if data loads slowly.
headlessbooleantrueRun browser hidden. Set false to watch it (useful for debugging).
portnumber4173Local server port. Auto-increments if taken.
debugbooleanfalseEnable verbose logging. Also available as --debug CLI flag.

Rendering engines

browser (default)

Standard headless Chromium. Works for 99% of React/Vite apps. Navigates to each route, waits for network idle, captures the DOM. Use this unless you have a specific reason not to.

advanced

Like browser, but also simulates page scroll to trigger lazy-loaded components. Also injects a <meta name="x-prerendered-by" content="revijs"> tag. Use this if your components only load when scrolled into view.

ssr

Minimal-wait mode for apps that already do server-side rendering. Skips the waitFor delay since content is already in the initial HTML.

CLI flags

FlagDescription
--config <path>Path to config file. Default: revi.config.js
--output <dir>Override outputDir from config
--debugEnable verbose logging

Programmatic API

Use ReviJs in your own Node.js scripts instead of the CLI. Useful for custom build pipelines and CI workflows.

prerender()

The main entry point. Renders all routes and writes static HTML to disk. Equivalent of running npx revijs.

import { prerender } from '@revijs/core';

await prerender({
  routes: ['/', '/about', '/contact'],
  outputDir: 'dist-prerendered',
  distDir: 'dist',
  engine: 'browser',
  waitFor: 1500,
});

renderAllPages()

Lower-level function. Accepts a fully resolved config, starts the server, boots the engine, renders all routes, tears down.

import { renderAllPages, loadConfig } from '@revijs/core';

const config = await loadConfig('revi.config.js');
await renderAllPages(config);

loadConfig()

Loads and validates revi.config.js, merges with defaults. Returns a fully resolved config object.

import { loadConfig } from '@revijs/core';

const config = await loadConfig('revi.config.js', {
  debug: true,
});

isBot() / detectBot()

Check if a user-agent string belongs to a known bot. Used internally by the middleware.

import { isBot, detectBot } from '@revijs/core';

isBot('Googlebot/2.1');     // true
isBot('Mozilla/5.0 ...');  // false
detectBot('GPTBot/1.0');    // 'gptbot'

saveHTML()

Maps a route path to a file and writes the HTML string to disk.

import { saveHTML } from '@revijs/core';

// /about → dist-prerendered/about/index.html
await saveHTML('/about', htmlString, 'dist-prerendered');

Middleware

Optional middleware that serves prerendered HTML to bots while passing all other requests through normally. Works with Express, Polka, and any Connect-compatible framework.

How it works

  1. A request comes in
  2. Middleware checks the User-Agent header against 30+ known bot patterns
  3. If it's a bot → reads the matching file from dist-prerendered/ and returns it
  4. If it's a human → calls next() and your app handles it normally
💡 This is completely optional. If you use a CDN or static host that handles bot routing differently, you don't need this.

Express

import express from 'express';
import { createMiddleware } from '@revijs/core';

const app = express();

// register BEFORE your static/SPA handler
app.use(createMiddleware({
  prerenderedDir: 'dist-prerendered',
  debug: false,
}));

app.use(express.static('dist'));
app.listen(3000);

Polka

import polka from 'polka';
import sirv from 'sirv';
import { createMiddleware } from '@revijs/core';

polka()
  .use(createMiddleware({ prerenderedDir: 'dist-prerendered' }))
  .use(sirv('dist', { single: true }))
  .listen(3000);

Options

OptionTypeDefaultDescription
prerenderedDirstring'dist-prerendered'Folder with your prerendered HTML files.
debugbooleanfalseLog which bot matched and which file was served.

Supported bots

GooglebotBingbotGPTBotClaudeBot PerplexityBotYandexbotBaiduspiderDuckDuckBot SlurpTwitterbotLinkedInBotSlackbot DiscordbotfacebookexternalhitAhrefsBotSemrushBot ApplebotAmazonbotWhatsAppTelegrambot Pinterest+ more

Examples

Real-world configs for common use cases. Copy and adjust for your project.

Blog / Content site

export default {
  routes: ['/', '/blog', '/blog/hello-world', '/blog/react-tips', '/about'],
  engine: 'browser',
  waitFor: 2000, // extra time for content to load
};

E-Commerce

export default {
  routes: ['/', '/products', '/products/sneaker-x1', '/categories/shoes'],
  // skip /cart /checkout /account — auth-protected routes
  engine: 'browser',
  waitFor: 1500,
};

SaaS landing page

export default {
  routes: ['/', '/pricing', '/features', '/changelog'],
  // /app/* and /dashboard/* intentionally skipped
  waitFor: 1200,
};

Automate with npm scripts

// package.json
{
  "scripts": {
    "build": "vite build",
    "prerender": "npm run build && npx revijs",
    "deploy": "npm run prerender && netlify deploy --dir=dist-prerendered"
  }
}

Adjusting waitFor

If content isn't appearing in the prerendered HTML, your app needs more time. Increase waitFor:

waitFor: 1200,  // fast APIs
waitFor: 3000,  // slow APIs or heavy data
waitFor: 5000,  // very slow — use sparingly

Debugging a route

Set headless: false to watch the browser open and --debug for verbose logs:

export default {
  headless: false,  // opens a visible browser window
  routes: ['/problem-route'],
  waitFor: 3000,
};
npx revijs --debug