Make your React SPA SEO-friendly without SSR complexity. Automatically generates static HTML snapshots for search engine crawlers.
npm install ssr-seo/ - Your app's root route remains yours
/ssr-seo/* routes only if you want the dashboard UI
bash
npm install ssr-seo
or
yarn add ssr-seo
or
pnpm add ssr-seo
`
$3
SSR-SEO requires React as a peer dependency:
`bash
npm install react react-dom
`
---
π Lovable Demo (Hackathon Build)
A live Lovable demo of SSR-SEO is available here:
https://important-cats.lovable.app/ssr-seo/status
Notes:
- Created during the Lovable hackathon the weekend of Jan 2
- Currently under freeze while judges review submissions
- May lag behind the latest package changes
---
π Integration Patterns
SSR-SEO supports two integration patterns depending on your app architecture.
---
$3
Best for apps built with Lovable or where you want SSR-SEO to manage the Supabase connection.
Step 1: Set environment variables
`env
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
`
Step 2: Wrap your app
`tsx
import { SSRSEOProvider } from 'ssr-seo';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
{/ YOUR routes - SSR-SEO does not create or own any routes /}
} />
} />
);
}
`
SSR-SEO will automatically create a Supabase client from the environment variables.
---
$3
Best for apps that already have a Supabase client or need full control over the connection.
Step 1: Create your Supabase client
`tsx
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key'
);
`
Step 2: Pass the client to SSRSEOProvider
`tsx
import { SSRSEOProvider } from 'ssr-seo';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { supabase } from './lib/supabase';
function App() {
return (
{/ YOUR routes - SSR-SEO does not create or own any routes /}
} />
} />
);
}
`
---
$3
The dashboard is completely opt-in. If you want the monitoring UI, add the routes yourself:
`tsx
import { SSRSEOProvider, SSRSEODashboardRoutes } from 'ssr-seo';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
{/ Your routes /}
} />
{/ Opt-in: SSR-SEO Dashboard under /ssr-seo/ */}
{SSRSEODashboardRoutes}
);
}
`
Dashboard routes provided:
| Route | Description |
|------|-------------|
| /ssr-seo/status | Setup verification and health checks |
| /ssr-seo/config | SEO configuration and settings |
| /ssr-seo/monitoring | Real-time monitoring and alerts |
---
π€ Important: Lovable Integration Guardrails (READ THIS FIRST)
If you are using Lovable, this section is critical.
$3
Lovable will not run init the way you expect. Instead, it tries to "do it itself" by scaffolding and restructuring, and that breaks SSR-SEO.
The good news: when this happens, the damage is usually contained to the /ssr-seo directory, so it is fixable. The bad news: undoing it costs Lovable prompts/credits.
$3
You must explicitly prompt Lovable to read and implement LOVABLE.md exactly as written.
Also tell Lovable:
- Do not scaffold or restructure the project
- Do not try to re-initialize anything
- Follow the steps in LOVABLE.md exactly
$3
Use this prompt verbatim:
`
Read LOVABLE.md and implement it exactly as written.
Do not scaffold or restructure the project.
Do not "init" the project in your own way.
Follow the LOVABLE.md steps exactly and only change what it instructs.
`
$3
If Lovable scaffolds on its own:
- It can break SSR-SEO (usually inside /ssr-seo)
- It is fixable, but it costs prompts and credits to undo
- That time and cost is avoidable if LOVABLE.md is followed first
$3
If you are using Lovable:
- LOVABLE.md is not optional
- It exists specifically to prevent tool-driven breakage
- Skipping it is the fastest way to lose time and credits
---
π API Reference
$3
The main provider component that wraps your application.
`tsx
interface SSRSEOProviderProps {
children: React.ReactNode;
// Supabase connection (choose one):
supabaseClient?: SupabaseClient; // Pass your own client (Pattern 2)
supabaseUrl?: string; // Or provide URL + key
supabaseAnonKey?: string; // (auto-reads from VITE_* env vars)
// Behavior options:
autoSetup?: boolean; // Auto-initialize on mount (default: true)
autoScan?: boolean; // Auto-scan for routes (default: true)
scanIntervalHours?: number; // Hours between scans (default: 24)
dashboardBasePath?: string; // Dashboard URL base (default: '/ssr-seo')
}
`
$3
Access SEO context from any component.
`tsx
import { useSSRSEO } from 'ssr-seo';
function MyComponent() {
const {
supabase, // The Supabase client (or null if not configured)
isSupabaseConnected, // Boolean: is Supabase available?
isBot, // Is current request from a bot?
config, // Current SEO config
refreshSnapshots, // Trigger snapshot refresh
runScan // Run an SEO scan
} = useSSRSEO();
if (!isSupabaseConnected) {
return Please configure Supabase to use SEO features;
}
return (
{isBot ? 'Bot detected!' : 'Human user'}
);
}
`
$3
Set page-specific SEO metadata.
`tsx
import { SEOHead } from 'ssr-seo';
function BlogPost({ post }) {
return (
<>
title={post.title}
description={post.excerpt}
keywords={post.tags}
ogImage={post.image}
ogType="article"
canonicalUrl={https://mysite.com/blog/${post.slug}}
/>
{/ content /}
>
);
}
`
$3
`tsx
import {
detectBot,
generateSitemap,
generateRobotsTxt,
validateMetaDescription,
validateTitle,
} from 'ssr-seo';
const isBot = detectBot(userAgent);
const sitemap = await generateSitemap('https://mysite.com');
const titleResult = validateTitle('My Page Title');
// { valid: true, length: 13, issues: [] }
`
---
βοΈ Database Setup
SSR-SEO uses Supabase for data storage. Required tables:
- seo_snapshots - Stores HTML snapshots
- seo_config - Stores configuration
- seo_monitoring - Stores scan results
- seo_alerts - Stores SEO issues
If using the dashboard, navigate to /ssr-seo/status to verify your setup. The dashboard will show you what is missing.
---
π― Common Use Cases
$3
`tsx
import { SEOHead } from 'ssr-seo';
function ProductPage({ product }) {
return (
<>
title={${product.name} | My Store}
description={product.description.slice(0, 160)}
ogImage={product.images[0]}
ogType="product"
/>
>
);
}
`
$3
`tsx
import { useSSRSEO } from 'ssr-seo';
function BlogLayout({ children }) {
const { refreshSnapshots, isSupabaseConnected } = useSSRSEO();
useEffect(() => {
if (newPostPublished && isSupabaseConnected) {
refreshSnapshots();
}
}, [newPostPublished, isSupabaseConnected]);
return {children};
}
`
$3
`tsx
import { generateSnapshot } from 'ssr-seo';
async function createSnapshot(path: string) {
const snapshot = await generateSnapshot(path);
console.log('Generated snapshot:', snapshot.metadata);
}
`
---
π§ Troubleshooting
$3
This appears when SSR-SEO cannot connect to Supabase. Fix it by:
- Pattern 1: Set VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY env vars
- Pattern 2: Pass supabaseClient prop to SSRSEOProvider
$3
- Verify Supabase connection at /ssr-seo/status
- Check that routes are being detected
- Ensure your components render content synchronously
- Check browser console for errors
$3
`tsx
import { detectBot } from 'ssr-seo';
console.log(detectBot('Googlebot/2.1')); // true
console.log(detectBot('Mozilla/5.0')); // false
`
$3
- Ensure SSRSEODashboardRoutes is added to your Routes
- Check you are navigating to /ssr-seo/status`
Made with love for the React community