Skip to content

TeamAlphabotz/Revijs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReviJs

The SPA prerender toolkit that actually works. Local-first, zero cloud, built for developers who are tired of their React app being invisible to Google.

npm downloads license node GitHub stars


The problem

Your React app looks great in a browser. But when Googlebot, GPTBot, or any other crawler visits it — they see an empty <div id="root"></div>. No content. No metadata. Nothing to index.

That is why your SEO is suffering. ReviJs fixes this.


How it works

ReviJs opens your app in a real headless Chromium browser, waits for all your data to load, captures the full rendered HTML, and either saves it as static files or serves it live to bots on demand — while your real users continue to get the normal React experience.

Two modes. You pick what fits your setup.


Quick start

npm install @revijs/core
npx revijs init
npm run build
npx revijs

Your prerendered HTML files are now in dist-prerendered/. Deploy them alongside your app and bots will see full content on every page.


Mode 1 — Static prerender (run once, deploy)

Best for static hosts like Netlify, Vercel, Cloudflare Pages.

npx revijs

ReviJs starts a local server, opens every route in a headless browser, waits for data to load, and writes the full HTML to disk.

dist-prerendered/
├── index.html
├── about/
│   └── index.html
└── blog/
    └── post-1/
        └── index.html

Deploy this folder. Done.


Mode 2 — Live middleware (real-time, always fresh)

Best for VPS, DigitalOcean, any Node.js server.

Add two lines to your existing server:

import express from 'express'
import { createLiveMiddleware } from '@revijs/core'

const app = express()

app.use(createLiveMiddleware({
  distDir: 'dist',
  ttl: 30,        // cache each page for 30 minutes
  debug: false,
}))

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

Now whenever a bot hits any route on your server — ReviJs renders that page in real time, serves the full HTML to the bot, caches it for 30 minutes, then renders fresh again next time. Your content is always up to date. No manual step. No cron job. Nothing to remember.

Real users hit the same server and get the normal React app — the middleware only intercepts bot requests.


Configuration

Create revi.config.js in your project root, or run npx revijs init to generate one.

export default {
  // Routes to prerender
  routes: ['/', '/about', '/blog/post-1'],

  // Or read routes from a sitemap (supports sitemap-index.xml)
  sitemapInput: null, // './sitemap.xml' or 'https://yoursite.com/sitemap-index.xml'

  // Or let ReviJs find routes automatically from your dist/ folder
  autoDiscover: false,

  // Or define routes dynamically from your own API
  // routes: async () => {
  //   const posts = await fetch('/api/posts').then(r => r.json())
  //   return posts.map(p => `/blog/${p.slug}`)
  // },

  engine: 'browser',        // 'browser' | 'advanced' | 'ssr'
  outputDir: 'dist-prerendered',
  distDir: 'dist',

  // Smart wait — pick one strategy
  waitFor: 1200,            // wait X ms after network idle (default)
  readyFlag: false,         // wait for window.__REVI_READY__ = true in your app
  waitForSelector: null,    // wait for a CSS selector, e.g. '#app[data-loaded]'

  // Output
  injectMeta: true,         // auto-inject missing og:title, og:description, og:image
  injectHead: null,         // inject custom HTML into <head> of every page
  sitemap: false,           // generate sitemap.xml after render. Set to your base URL for absolute URLs
  score: true,              // print SEO score for each page after rendering
  report: true,             // save revi-report.json after each run
  cache: false,             // skip unchanged routes on next run (uses content hash)
  retries: 2,               // retry failed routes N times before giving up
  parallel: 1,              // number of routes to render at the same time

  headless: true,
  port: 4173,
  debug: false,
};

All options

Option Type Default Description
routes string[] | async fn ['/'] Routes to prerender, or async function that returns routes
sitemapInput string null Path or URL to sitemap.xml or sitemap-index.xml
autoDiscover boolean false Scan dist/ and discover routes automatically
engine string 'browser' Rendering engine: browser, advanced, ssr
outputDir string 'dist-prerendered' Where to write HTML files
distDir string 'dist' Your built SPA directory
waitFor number 1200 ms to wait after network idle
readyFlag boolean false Wait for window.__REVI_READY__ = true
waitForSelector string null Wait for a CSS selector to appear in DOM
injectMeta boolean true Auto-inject missing og: and twitter: meta tags
injectHead string null Custom HTML to inject into <head> on every page
sitemap boolean|string false Generate sitemap.xml. Set to base URL for absolute URLs
score boolean true Print SEO score report in terminal
report boolean true Save revi-report.json with full results
cache boolean false Skip routes whose content hasn't changed since last run
retries number 2 Retry failed routes before marking them as failed
parallel number 1 Render multiple routes simultaneously
headless boolean true Run browser in background
port number 4173 Local server port
debug boolean false Verbose logging

CLI commands

# Prerender all routes
npx revijs

# Generate a starter config file
npx revijs init

# Watch dist/ for changes and re-render automatically
npx revijs watch

# Open the dashboard in your browser
npx revijs ui

# Print the last prerender report
npx revijs report

# Deploy your output to a hosting provider
npx revijs deploy --target=netlify
npx revijs deploy --target=vercel
npx revijs deploy --target=cloudflare

# Force re-render everything, ignore cache
npx revijs --no-cache

Smart wait strategy

The default waitFor: 1200 is just a timer — it waits 1.2 seconds and hopes your data has loaded. For production use, tell ReviJs exactly when your app is ready.

Option 1 — Ready flag (recommended)

In your React app, set a flag when everything is loaded:

useEffect(() => {
  Promise.all([fetchPosts(), fetchUser()]).then(() => {
    window.__REVI_READY__ = true
  })
}, [])

In your config:

readyFlag: true,
waitFor: 0,

ReviJs will wait for that flag instead of guessing.

Option 2 — Wait for a DOM element

waitForSelector: '#app[data-loaded="true"]',

ReviJs waits for that element to appear before capturing HTML.


Cache layer

On large sites with many routes, re-rendering everything on every run is slow. Enable caching:

cache: true,

ReviJs hashes the rendered HTML for each route. On the next run, if the hash matches — it skips that route entirely. Only changed pages get re-rendered. A site with 500 routes where 10 changed will only render those 10.

The cache is stored in dist-prerendered/.revi-cache.json.


SEO scoring

After each render, ReviJs scores every page and prints a report:

  SEO Scores
  ────────────────────────────────────────────
  100%   /
   80%   /about
   60%   /blog/post-1
           missing: og:image, meta description
  ────────────────────────────────────────────
  avg 80%

Checks performed on each page: title tag, meta description, og:title, og:description, og:image, h1 tag, image alt attributes, canonical link, lang attribute.


Prerender report

After each run, a revi-report.json file is saved to your output directory:

{
  "generatedAt": "2026-03-20T11:00:00.000Z",
  "summary": {
    "total": 5,
    "ok": 5,
    "failed": 0,
    "avgScore": 84
  },
  "routes": [
    {
      "route": "/",
      "ms": 1823,
      "score": 100,
      "passed": ["Has <title>", "Has og:title", "Has h1"],
      "failed": []
    }
  ],
  "errors": []
}

View the last report anytime:

npx revijs report

Meta tag injection

ReviJs automatically adds missing meta tags to each rendered page based on the page content. If your page has a <title> and an <h1> but no og: tags, ReviJs will inject them:

<meta property="og:title" content="Your Page Title" />
<meta property="og:description" content="First heading content" />
<meta property="og:image" content="/first-image-found.png" />
<meta name="twitter:card" content="summary_large_image" />

You can also inject custom HTML into every page's <head>:

injectHead: '<link rel="preconnect" href="https://fonts.googleapis.com" />',

Sitemap support

Generate a sitemap after prerendering:

sitemap: 'https://yoursite.com',

Creates dist-prerendered/sitemap.xml with all rendered routes.

Read routes from an existing sitemap:

sitemapInput: 'https://yoursite.com/sitemap-index.xml',

Supports both regular sitemap.xml and sitemap-index.xml. ReviJs fetches all child sitemaps from the index and extracts every route automatically.


Vite plugin

Auto-prerender after every npm run build:

// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { reviPlugin } from '@revijs/core'

export default defineConfig({
  plugins: [
    react(),
    reviPlugin(),
  ],
})

After this, running npm run build will automatically prerender your app. No separate command needed.


Dashboard

npx revijs ui

Opens a local web dashboard showing render status, time per route, SEO scores per page, and any errors. You can trigger a new prerender run directly from the dashboard.


Static middleware

If you are running a Node.js server and want to serve prerendered files to bots from disk:

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

const app = express()
app.use(createMiddleware({ prerenderedDir: 'dist-prerendered' }))
app.use(express.static('dist'))
app.listen(3000)

Live middleware

If you want real-time on-demand rendering with no pre-build step:

import express from 'express'
import { createLiveMiddleware } from '@revijs/core'

const app = express()
app.use(createLiveMiddleware({
  distDir: 'dist',
  ttl: 30,           // cache rendered HTML for 30 minutes
  maxPages: 200,     // max number of pages to keep in cache
  waitFor: 1200,
  injectMeta: true,
  debug: false,
}))
app.use(express.static('dist'))
app.listen(3000)
Option Default Description
distDir 'dist' Your built SPA directory
ttl 30 Minutes to cache each rendered page
maxPages 200 Max pages in memory cache
waitFor 1200 ms to wait after network idle
readyFlag false Wait for window.__REVI_READY__
waitForSelector null Wait for CSS selector
injectMeta true Inject missing og: tags
debug false Log every bot request and cache hit

Programmatic API

import { prerender } from '@revijs/core'

await prerender({
  routes: ['/', '/about', '/contact'],
  outputDir: 'dist-prerendered',
  injectMeta: true,
  score: true,
  cache: true,
  retries: 2,
})

Full API:

import {
  prerender,
  loadConfig,
  renderAllPages,
  renderPage,
  saveHTML,
  createMiddleware,
  createLiveMiddleware,
  isBot,
  detectBot,
  generateSitemap,
  routesFromSitemap,
  discoverRoutes,
  injectMeta,
  scoreHTML,
  deploy,
  reviPlugin,
  watchMode,
} from '@revijs/core'

Supported bots

ReviJs detects 30+ crawlers out of the box.

Most common

Bot Company Type
googlebot Google Search engine
bingbot Microsoft Search engine
gptbot OpenAI AI crawler
claudebot Anthropic AI crawler
perplexitybot Perplexity AI AI crawler
facebookexternalhit Meta Social preview
twitterbot X Social preview
linkedinbot LinkedIn Social preview
yandexbot Yandex Search engine
baiduspider Baidu Search engine
View all supported bots

Search engines googlebot google-inspectiontool bingbot yandexbot baiduspider duckduckbot slurp sogou exabot rogerbot mj12bot dotbot ia_archiver

AI crawlers gptbot chatgpt-user claudebot claude-web anthropic-ai perplexitybot cohere-ai amazonbot applebot

SEO tools ahrefsbot semrushbot

Social and preview facebookexternalhit twitterbot linkedinbot slackbot discordbot telegrambot whatsapp vkshare pinterest tumblr flipboard


Supported bot list in code

const BOT_PATTERNS = [
  // Search engines
  'googlebot', 'google-inspectiontool', 'bingbot', 'slurp', 'duckduckbot',
  'baiduspider', 'yandexbot', 'sogou', 'exabot', 'facebot', 'ia_archiver',
  'mj12bot', 'dotbot', 'ahrefsbot', 'semrushbot', 'rogerbot',

  // AI / LLM crawlers
  'gptbot', 'chatgpt-user', 'claudebot', 'claude-web', 'perplexitybot',
  'cohere-ai', 'amazonbot', 'applebot', 'anthropic-ai',

  // Social / preview
  'facebookexternalhit', 'twitterbot', 'linkedinbot', 'slackbot', 'discordbot',
  'telegrambot', 'whatsapp', 'vkshare', 'pinterest', 'tumblr', 'flipboard',

  // Generic
  'spider', 'crawler', 'scraper', 'bot/', '+http',
]

Hosting compatibility

Platform Static mode Live middleware
VPS / DigitalOcean Yes Yes
Netlify Yes No
Vercel Yes No
Cloudflare Pages Yes No
AWS EC2 Yes Yes
Shared hosting (cPanel) No No
AWS Lambda / serverless No No

Live middleware requires a persistent Node.js process. Static mode works everywhere you can upload files.


Rendering engines

browser — Standard headless Chromium. Works for almost every React and Vite app. This is what you should use unless you have a specific reason not to.

advanced — Same as browser but also simulates page scroll to trigger lazy-loaded components. Injects a <meta name="x-prerendered-by" content="revijs"> tag into the output.

ssr — Minimal-wait mode for apps that already have server-side rendering. Skips the waitFor delay.


FAQ

Does it work with Vue, Svelte, or other frameworks? Yes. ReviJs renders whatever the browser renders. It does not care about your framework.

Does my app need any changes? No. ReviJs works with your existing built output. Optionally you can add window.__REVI_READY__ = true for smarter wait detection, but it is not required.

Will it slow down my users? No. The live middleware only intercepts requests from known bots. Real users are never affected.

Is the browser engine exposed to users? No. Playwright Chromium is an internal implementation detail. It installs automatically and is never exposed in the API.

Why is my SEO score not 100? The score checks for meta description, canonical link, image alt attributes and other tags that your app may not have. These are real SEO issues worth fixing in your app, not ReviJs bugs.


Team

Name Role
Utkarsh Dubey Core Developer
Adarsh Core Developer

Telegram: @thealphabotz · @alphabotzchat


MIT License — free to use, modify, and distribute.

About

No description, website, or topics provided.

Resources

License

Stars

4 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors