Migration Guide
Moving to prestruct from other solutions.
From Create React App (CRA)
What Changes
| CRA | Prestruct |
|---|---|
react-scripts build |
npm run build (3-step) |
| Client-only | Prerendered static HTML |
| No SEO meta handling | Automatic meta injection |
| SPA fallback needed | Per-route HTML files |
Steps
- Add Vite
npm install vite @vitejs/plugin-react - Create vite.config.js
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], build: { outDir: 'dist' } }) - Add prestruct files
- Copy
scripts/inject-brand.jsandscripts/prerender.js - Create
ssr.config.js - Create
AppLayout.jsx(no BrowserRouter!)
- Copy
- Update package.json
{ "scripts": { "dev": "vite", "build": "vite build && node scripts/inject-brand.js && node scripts/prerender.js", "preview": "vite preview" } } - Deploy to Cloudflare Pages instead of static hosting
From Next.js Static Export
What Changes
| Next.js | Prestruct |
|---|---|
next build && next export |
npm run build |
_next/ folder |
assets/ (hashed) |
getStaticProps |
Build-time data in ssr.config.js |
| Image Optimization API | Use <img srcset> |
| API Routes | Cloudflare Pages Functions |
Steps
- Remove Next.js
npm uninstall next react - Add Vite + React
npm install react react-dom react-router-dom npm install -D vite @vitejs/plugin-react -
Adapt Pages
// Next.js page export async function getStaticProps() { const data = await fetchData() return { props: { data } } } // Prestruct - move to ssr.config.js or build script const routes = [ { path: '/', meta: {...} }, // ... dynamic routes generated at build time ] -
Update Image Usage
// Next.js <Image src="/img.png" width={800} height={600} /> // Prestruct <img src="/img.png" srcset="/img-400.png 400w, /img-800.png 800w" sizes="(max-width: 600px) 400px, 800px" width={800} height={600} loading="lazy" /> - Remove Next.js config, add Vite config
From Gatsby
What Changes
| Gatsby | Prestruct |
|---|---|
| GraphQL data layer | Static config / build scripts |
| gatsby-node.js | ssr.config.js routes |
| Head API | usePageMeta hook |
| Image Plugin | Manual srcset |
Steps
- Remove Gatsby
npm uninstall gatsby react gatsby-plugin-* - Install Vite + React
npm install react react-dom react-router-dom npm install -D vite @vitejs/plugin-react -
Migrate data
// gatsby-node.js exports.createPages = async ({ actions }) => { const posts = await getPosts() posts.forEach(post => { actions.createPage({ path: `/blog/${post.slug}`, component: './src/templates/post.jsx', context: { post } }) }) } // ssr.config.js const posts = await getPosts() // Call at build time routes: [ ...posts.map(post => ({ path: `/blog/${post.slug}/`, meta: { title: post.title } })) ] -
Update image handling
Gatsby’s image processing is removed. Use standard
<img>with srcset or a Vite plugin.
From React Static (Babel)
What Changes
| React Static | Prestruct |
|---|---|
react-static build |
npm run build |
getRoutes |
ssr.config.js routes |
getSiteData |
Build-time fetch in ssr.config.js |
Steps
- Remove React Static
npm uninstall react-static -
Migrate routes
// react-static.config.js export default { getRoutes: async () => { const posts = await fetch('/api/posts').then(r => r.json()) return [ { path: '/', component: 'src/pages/Home.jsx' }, ...posts.map(post => ({ path: `/blog/${post.slug}`, component: 'src/pages/BlogPost.jsx', getData: () => ({ post }) })) ] } } // ssr.config.js const posts = await fetch('/api/posts').then(r => r.json()) export default { routes: [ { path: '/', meta: {...} }, ...posts.map(post => ({ path: `/blog/${post.slug}/`, meta: { title: post.title } })) ] }
From Astro
What Changes
| Astro | Prestruct |
|---|---|
.astro files |
.jsx React components |
| Frontmatter | React hooks |
| Collection API | Build-time fetch |
| Islands (Astro) | Islands (prestruct) |
Steps
-
Add React to Astro (or convert to full React)
If keeping Astro with React:
- Add
prestructas prerendering layer
If converting to pure React:
npm uninstall astro @astrojs/react npm install react react-dom react-router-dom npm install -D vite @vitejs/plugin-react - Add
-
Migrate Components
--- // Astro component const { title } = Astro.props --- <h1>{title}</h1> <!-- React component --> export default function Heading({ title }) { return <h1>{title}</h1> } -
Migrate Data Fetching
--- // In Astro frontmatter const data = await fetch('https://api.example.com').then(r => r.json()) --- <!-- In React - do this in ssr.config.js or build script -->
Common Migration Patterns
Dynamic Data at Build Time
// ssr.config.js - fetch once at build time
const posts = await fetch('https://api.example.com/posts')
.then(r => r.json())
export default {
routes: [
{ path: '/blog/', meta: {...} },
...posts.map(post => ({
path: `/blog/${post.slug}/`,
meta: {
title: post.title,
description: post.excerpt
}
}))
]
}
Environment-Based Config
// ssr.config.js
const isProd = process.env.NODE_ENV === 'production'
export default {
siteUrl: isProd
? 'https://example.com'
: 'http://localhost:5173',
// ...
}
Shared Components
Most UI libraries work with prestruct. Test for:
- SSR compatibility (no window/document at render time)
- No inline styles causing hydration mismatch
After Migration
- Test locally:
npm run preview - Check SEO: Verify meta tags, sitemap, canonical URLs
- Lighthouse audit: Should score 90+ on all metrics
- Deploy: Push to Cloudflare Pages