Migrate to Astro
When to Use
- Migrating from Next.js, Nuxt, Gatsby, or other frameworks
- Upgrading between major Astro versions
- Converting a static HTML site to Astro
- Moving from Create React App to Astro
Instructions
1. Identify Migration Path
| From | Complexity | Key Changes |
|---|---|---|
| Static HTML | Low | Wrap in .astro, add layouts |
| Next.js (Pages) | Medium | Route structure similar, convert JSX |
| Next.js (App) | Medium-High | Server components map well to Astro |
| Nuxt | Medium | Vue components work with integration |
| Gatsby | Medium | GraphQL → Content Collections |
| Create React App | Medium | Add routing, convert to islands |
| Astro v4 → v5 | Low-Medium | Content Layer changes begin |
| Astro v5 → v6 | Medium | Content collections, Astro.glob(), and runtime changes |
2. Project Setup
Create new Astro project alongside existing:
npm create astro@latest my-astro-site
Or convert in-place by installing Astro:
npm install astro
Update package.json scripts:
{
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview"
}
}
If upgrading to Astro 6, verify the runtime first:
node --version
Astro 6 requires Node 22.12.0 or newer. Also upgrade official integrations and adapters alongside Astro to avoid version skew.
3. Migration Patterns
Static HTML → Astro
- Rename
.htmlto.astro - Move to
src/pages/ - Extract repeated sections to
src/layouts/ - Convert to components as needed
---
// src/layouts/Layout.astro
const { title } = Astro.props
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
Next.js → Astro
Pages Router:
| Next.js | Astro |
|---|---|
pages/index.js |
src/pages/index.astro |
pages/blog/[slug].js |
src/pages/blog/[slug].astro |
getStaticProps |
Frontmatter code block |
getStaticPaths |
getStaticPaths() export |
_app.js |
src/layouts/Layout.astro |
next/image |
astro:assets Image component |
next/link |
Standard <a> tags |
Convert a Next.js page:
// Next.js: pages/blog/[slug].js
export async function getStaticPaths() {
const posts = await getPosts()
return { paths: posts.map(p => ({ params: { slug: p.slug } })) }
}
export async function getStaticProps({ params }) {
const post = await getPost(params.slug)
return { props: { post } }
}
export default function Post({ post }) {
return <article><h1>{post.title}</h1></article>
}
---
// Astro: src/pages/blog/[slug].astro
import { getCollection, render } from 'astro:content'
export async function getStaticPaths() {
const posts = await getCollection('blog')
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}))
}
const { post } = Astro.props
const { Content } = await render(post)
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
Keep React components:
npx astro add react
Then use with client directives:
---
import InteractiveWidget from '../components/InteractiveWidget.tsx'
---
<InteractiveWidget client:visible />
Nuxt → Astro
npx astro add vue
| Nuxt | Astro |
|---|---|
pages/ |
src/pages/ (similar) |
components/ |
src/components/ |
layouts/ |
src/layouts/ |
nuxt.config.ts |
astro.config.mjs |
| Auto-imports | Explicit imports |
Vue components work directly:
---
import MyVueComponent from '../components/MyVueComponent.vue'
---
<MyVueComponent client:load />
Gatsby → Astro
| Gatsby | Astro |
|---|---|
| GraphQL queries | Content Collections |
gatsby-image |
astro:assets Image |
gatsby-plugin-* |
Astro integrations |
| MDX pages | MDX in content collections |
Convert GraphQL to Content Collections:
// Gatsby: gatsby-node.js with GraphQL
// → Astro: src/content.config.ts with a loader and schema
import { defineCollection } from 'astro:content'
import { glob } from 'astro/loaders'
import { z } from 'astro/zod'
const blog = defineCollection({
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
// Match your Gatsby frontmatter fields
}),
})
export const collections = { blog }
4. Astro Version Upgrades
Upgrade command:
npx @astrojs/upgrade
v5 → v6 Key Changes:
- Node 22.12.0+ is required
- Legacy content collections support is removed
src/content/config.tsmoves tosrc/content.config.ts- Each collection now needs a
loader zshould come fromastro/zodentry.idreplaces legacyslugusage in collection routingentry.render()becomesrender(entry)Astro.glob()is removed in favor ofimport.meta.glob()orgetCollection()<ViewTransitions />is removed; use<ClientRouter />Astro.siteinsidegetStaticPaths()should becomeimport.meta.env.SITE
Astro 6 upgrade checklist:
- Upgrade Astro and official integrations/adapters together
- Verify Node 22.12.0+ locally and in CI/deployment
- Move
src/content/config.tstosrc/content.config.ts - Add a
loaderto every collection - Replace
import { defineCollection, z } from 'astro:content'withimport { defineCollection } from 'astro:content'andimport { z } from 'astro/zod' - Replace
post.slugwithpost.id - Replace
post.render()withrender(post) - Replace
Astro.glob()withimport.meta.glob()orgetCollection() - Replace
<ViewTransitions />with<ClientRouter />
Check migration guide: docs.astro.build/guides/upgrade-to/v6
5. Common Migration Tasks
Move static assets
public/images/ → public/images/ (same)
src/assets/ → src/assets/ (processed by Vite)
Update image imports
---
import { Image } from 'astro:assets'
import heroImage from '../assets/hero.jpg'
---
<Image src={heroImage} alt="Hero" width={800} height={400} />
Convert CSS
- Global CSS → Import in layout
- CSS Modules → Work as-is
- Styled Components → Use with React integration
- Tailwind →
npx astro add tailwind
Environment variables
// Next.js
NEXT_PUBLIC_API_URL
// Astro
PUBLIC_API_URL // Client-side
API_SECRET // Server-side only
Replace Astro.glob()
---
// Before
const posts = await Astro.glob('./posts/*.md')
// After
const posts = Object.values(import.meta.glob('./posts/*.md', { eager: true }))
---
For content collections, prefer getCollection() instead of raw globs.
6. Verification Checklist
- Local and deployment environments meet the required Node version
- All routes render correctly
- Content collections build without warnings
- Images load and are optimized
- Interactive components hydrate
- SEO meta tags present
- Build completes without errors
- Links work (no 404s)
- Forms submit correctly
- Performance improved or maintained