Getting Started
Build a fully-prerendered React app for Cloudflare Pages in minutes.
Prerequisites
- Node.js 18+
- npm or pnpm
- Cloudflare Pages project (or GitHub account to create one)
Quick Start
1. Create Your Project
npx degit dhaupin/prestruct/example my-prestruct-app
cd my-prestruct-app
npm install
2. Configure Your Site
Edit ssr.config.js in your project root:
export default {
siteUrl: 'https://your-domain.com',
siteName: 'Your Site Name',
author: 'Your Name',
tagline: 'Your tagline here',
ogImage: 'https://your-domain.com/og-image.jpg',
keywords: 'keyword one, keyword two',
appLayoutPath: '/src/AppLayout.jsx',
routes: [
{
path: '/',
priority: '1.0',
changefreq: 'weekly',
meta: {
title: 'Home | Your Site',
description: 'Your homepage description (150-160 chars)',
}
},
{
path: '/about/',
priority: '0.8',
changefreq: 'monthly',
meta: {
title: 'About | Your Site',
description: 'Learn more about us...',
}
}
],
buildJsonLd() {
return [
{
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Your Org',
url: 'https://your-domain.com',
}
]
}
}
3. Add Your Pages
Create your page components in src/pages/:
// src/pages/Home.jsx
import usePageMeta from '../hooks/usePageMeta.js'
export default function Home() {
usePageMeta({
path: '/',
title: 'Home | Your Site',
description: 'Your homepage description',
})
return (
<div>
<h1>Welcome to My Site</h1>
</div>
)
}
4. Define Your Routes
Update src/AppLayout.jsx with your routes:
// src/AppLayout.jsx - NOTE: No BrowserRouter here!
import { Routes, Route, useLocation } from 'react-router-dom'
import { useEffect } from 'react'
import Home from './pages/Home'
import About from './pages/About'
// Scroll to top on navigation
function ScrollToTop() {
const { pathname } = useLocation()
useEffect(() => {
if (typeof window !== 'undefined') {
window.scrollTo(0, 0)
}
}, [pathname])
return null
}
export default function AppLayout() {
return (
<>
<ScrollToTop />
<nav>{/* Your nav here */}</nav>
<main>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about/" element={<About />} />
</Routes>
</main>
<footer>{/* Your footer here */}</footer>
</>
)
}
Then wrap it in BrowserRouter in src/App.jsx:
// src/App.jsx
import { BrowserRouter } from 'react-router-dom'
import AppLayout from './AppLayout'
export default function App() {
return (
<BrowserRouter>
<AppLayout />
</BrowserRouter>
)
}
5. Build
npm run build
This runs three steps:
vite build- Creates your JS/CSS bundlesnode scripts/inject-brand.js- Injects global SEO metanode scripts/prerender.js- Prerenders each route to HTML
6. Preview Locally
npm run preview
Opens your prerendered site at http://localhost:4173.
7. Deploy to Cloudflare Pages
- Push your code to GitHub
- Go to Cloudflare Dashboard → Pages
- Connect your GitHub repository
- Set build settings:
- Build command:
npm run build - Build output directory:
dist - Node.js version:
18
- Build command:
- Deploy!
Project Structure
my-app/
├── ssr.config.js # Your config (routes, meta, schema)
├── package.json # Build: "vite build && node scripts/inject-brand.js && node scripts/prerender.js"
├── vite.config.js # Standard Vite config
├── public/ # Static assets
│ ├── _headers # Cloudflare security headers
│ ├── _redirects # Cloudflare redirects
│ └── favicon.ico
├── src/
│ ├── main.jsx # Client entry (hydrateRoot)
│ ├── App.jsx # BrowserRouter wrapper
│ ├── AppLayout.jsx # Routes + layout (NO BrowserRouter!)
│ ├── AppIslands.jsx # Island registry
│ ├── hooks/
│ │ └── usePageMeta.js # SEO meta hook
│ ├── islands/ # Dynamic island components
│ └── pages/ # Your page components
├── scripts/
│ ├── inject-brand.js # Injects global meta
│ └── prerender.js # Prerenders routes to HTML
└── dist/ # Build output (generated)
├── index.html
├── about/
│ └── index.html
├── sitemap.xml
└── 404.html
Environment Variables
| Variable | Description | Default |
|---|---|---|
VITE_SITE_URL |
Production site URL | From ssr.config.js |
PRODUCTION |
Enable production mode | false |