Routerino is a React router with static HTML generation and built-in SEO support.
Live demos: raymondsseptic.com, kissimmeekastawayvilla.com, nerdswithkeyboards.com, and logbook-ed.com
Why
React SPAs do not always give search engines complete HTML to index. Frameworks such as Next.js and Remix solve this, but adopting one also brings its conventions and application structure.
Routerino handles routing as a library and provides a Vite plugin for generating static HTML at build time. Generated pages include meta tags, Open Graph tags, canonical URLs, sitemap.xml, and robots.txt. The client-side application still uses standard <a> tags and has no added runtime dependencies.
Quick Start
import Routerino from "routerino"; export const routes = [ { path: "/", element: <p>This is the home page!</p>, title: "My Home Page!", description: "Welcome to my home page!", }, { path: "/blog/my-first-post/", element: ( <article> <h1>My First Post</h1> <p>Lorem ipsum...</p> </article> ), title: "My First Post", description: "The first post on my new home page!", tags: [{ property: "og:type", content: "article" }], }, ]; <Routerino title="Example.com" routes={routes} />;
What It Does
| Feature | Routerino | React Router | Next.js | Gatsby | Remix |
|---|---|---|---|---|---|
| SSG (static HTML) | ✅ Built-in Vite plugin | ❌ | ✅ Built-in | ✅ Core feature | ✅ Built-in |
| Meta tags / Open Graph | ✅ Per-route, automatic | ❌ | ✅ Metadata API | ✅ Via plugin | ✅ meta export |
| Sitemap generation | ✅ Automatic | ❌ | ❌ Manual | ✅ Via plugin | ❌ Manual |
| Canonical URLs | ✅ Automatic | ❌ | ❌ Manual | ❌ Manual | ❌ Manual |
Standard <a> tags |
✅ No <Link> needed |
❌ Must use <Link> |
❌ Must use <Link> |
❌ Must use <Link> |
❌ Must use <Link> |
| Runtime dependencies | 0 (peer: react) | 3+ | 50+ | 100+ | 20+ |
| Prerender support | ✅ Built-in | ❌ | ✅ ISR | ✅ SSG | ✅ Built-in |
| Library, not framework | ✅ | ✅ | ❌ | ❌ | ❌ |
Installation
Routerino requires React, React DOM, and PropTypes as peer dependencies (typically already installed). Supports React 18/19 and Node.js 18+.
Usage
Route Configuration
Routes are plain objects. At minimum, each needs a path and element:
export const routes = [ { path: "/", element: <HomePage />, title: "Home", description: "Welcome to our site", }, { path: "/about/", element: <AboutPage />, title: "About Us", description: "Learn more about our team", imageUrl: "/images/about-og.jpg", }, { path: "/products/:id/", // Dynamic routes are not statically generated element: <ProductPage />, }, ];
Links are standard <a> tags. Routerino intercepts same-origin clicks for SPA navigation; the browser handles everything else (cross-origin links, mailto:, target="_blank", file downloads, etc.) automatically.
The useRouterino Hook
Access router state from any component:
import { useRouterino } from "routerino"; function ProductPage() { const { currentRoute, params, routePattern, updateHeadTag } = useRouterino(); useEffect(() => { updateHeadTag({ name: "description", content: `Product: ${params.id}` }); }, [params.id]); return <div>Product: {params.id}</div>; }
The hook returns currentRoute, params, routePattern, and updateHeadTag.
updateHeadTag
Create or update head tags at any time. Matches existing tags by attribute to avoid duplicates:
updateHeadTag({ name: "description", content: "Some description..." }); updateHeadTag({ tag: "link", rel: "apple-touch-icon", href: "/icon.png" }); updateHeadTag({ property: "og:site_name", content: "Your Brand" });
Set soft: true to skip overwriting existing values. Use innerHTML for structured data and non-self-closing tags. See docs/seo-guide.md for full details.
Programmatic Navigation
window.history.pushState({}, "", "/about/"); window.dispatchEvent(new PopStateEvent("popstate"));
ErrorBoundary
import { ErrorBoundary } from "routerino"; <ErrorBoundary fallback={<div>Something went wrong.</div>} errorTitleString="Error | My Site" debug={window.location.hostname === "localhost"} > <MyComponent /> </ErrorBoundary>;
Static Site Generation (Routerino Forge)
Export the routes and the same app shell that the browser renders, then point Forge at that module:
// vite.config.js import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { routerinoForge } from "routerino/forge"; export default defineConfig({ plugins: [ react(), routerinoForge({ baseUrl: "https://example.com", routes: "./src/App.jsx", }), ], });
Requirements
baseUrlis required and must be an HTTP(S) origin such ashttps://example.com, with no path, query, hash, or trailing slashindex.htmlmust have<div id="root"></div>- The configured module must export
routes; exporting its app component asAppor the default export preserves the full layout and context during SSG - Static route paths must begin with
/and cannot contain empty segments, queries, hashes, backslashes, path traversal, or encoded separators - Dynamic routes (with
:param) are automatically skipped
If the module exports routes but no app component, Forge deliberately renders the matched route element by itself. This route-only mode is useful for simple sites, but it omits any layout or providers that live outside that element. See the full setup for the recommended app structure and hydration entry point.
Build Output
- Static HTML for every route with full meta tags
- Dual file generation (
/about.html+/about/index.html) for host compatibility; both copies point to the canonical style selected byuseTrailingSlash - Automatic
sitemap.xmlandrobots.txt 404.htmlat root for custom error pages- Canonical URL and
og:urlmeta tags on every page - Root-relative social images resolved against
baseUrl; absolute image URLs are preserved - A failed route render, 404 render, unsafe route path, invalid configuration, or missing generated file fails the Vite build
Generating Routes from Data
// Build-time data fetching is fully supported const blogPosts = await fetch("https://api.example.com/posts").then((r) => r.json() ); export const routes = [ { path: "/", element: <HomePage />, title: "Home" }, ...blogPosts.map((post) => ({ path: `/blog/${post.slug}/`, element: <BlogPost post={post} />, title: post.title, description: post.excerpt, imageUrl: post.featuredImage, })), ];
Each generated route gets its own static HTML page with proper meta tags and is automatically included in sitemap.xml.
Who Is This For?
Routerino is intended for React content sites, marketing pages, and JAMstack applications that need static HTML, meta tags, and sitemaps without adopting a framework such as Next.js or Remix. A dashboard or authenticated application may not need static generation; a general-purpose client-side router may be enough in that case.
TypeScript
TypeScript definitions are included. Routes are typed as RouteConfig[]:
import type { RouteConfig } from "routerino"; export const routes: RouteConfig[] = [ { path: "/", element: <HomePage />, title: "Home", description: "Welcome" }, ];
Documentation
- Getting Started: Full React example and Preact setup
- SEO Guide: Canonical URLs, social previews, JSON-LD, and hash links
- Image Optimization: Using
vite-plugin-image-optimizer - Accessibility: ESLint accessibility setup for Lighthouse scores
- Vendoring: Including Routerino directly in your project
- Additional Resources: External SEO and performance links
API Reference
<Routerino> Props
| Prop | Type | Default | Description |
|---|---|---|---|
title |
string |
"" |
Site title appended to page titles |
routes |
RouteConfig[] |
[default] |
Array of route configurations |
separator |
string |
" | " |
Title separator between page and site title |
notFoundTemplate |
ReactNode |
Built-in 404 | 404 page template |
notFoundTitle |
string |
"Page not found [404]" |
404 page title |
errorTemplate |
ReactNode |
Built-in 500 | Error page template |
errorTitle |
string |
"Page error [500]" |
Error page title |
useTrailingSlash |
boolean |
true |
Use trailing slashes in canonical URLs |
usePrerenderTags |
boolean |
false |
Include prerender meta tags for crawlers |
baseUrl |
string |
null |
Base URL for canonical tags (no trailing slash) |
imageUrl |
string |
null |
Default site-wide social image URL |
touchIconUrl |
string |
null |
PWA homescreen icon URL |
debug |
boolean |
false |
Enable console logging |
ignorePatterns |
string[] |
[] |
URL patterns to skip SPA routing |
RouteConfig Object
| Field | Type | Required | Description |
|---|---|---|---|
path |
string |
Yes | Route path. Must start with /. Supports :param. |
element |
ReactElement |
Yes | Rendered JSX element, such as <AboutPage /> |
title |
string |
No | Page title (site title appended automatically) |
description |
string |
No | Meta description |
imageUrl |
string |
No | Social preview image for this route |
tags |
HeadTag[] |
No | Additional head tags (OG, JSON-LD, etc.) |
HeadTag Object
Common attributes: tag, name, property, content, rel, href, soft, innerHTML. Supports all standard HTML attributes. See updateHeadTag for details.
routerinoForge Options
| Option | Type | Default | Description |
|---|---|---|---|
baseUrl |
string |
Required | HTTP(S) origin; no path, query, hash, or slash |
routes |
string |
"./src/routes.jsx" |
Path to routes file |
template |
string |
"index.html" |
Built HTML path relative to outputDir |
outputDir |
string |
"dist" |
Build output directory |
generateSitemap |
boolean |
true |
Generate sitemap.xml and robots.txt |
useTrailingSlash |
boolean |
true |
Set to false for /about instead of /about/ |
verbose |
boolean |
false |
Enable detailed build logging |
ssgCacheDir |
string |
"node_modules/.cache/routerino-forge/ssg" |
Parent for isolated temporary SSG bundles |
Contributing
Contributions are welcome. Please create an issue or submit a pull request. Keep it simple!
License
Routerino is MIT licensed.