Gotchas
Common mistakes, stale assumptions, and things that trip people up when learning or migrating to Astro.
Stale assumptions from older Astro versions
output: 'hybrid' no longer exists
Astro 5 removed hybrid mode. Current model:
output: 'static'(default) withprerender = falseon specific routesoutput: 'server'withprerender = trueon specific routes
If you see old guides mentioning hybrid, ignore that advice.
Astro 6 requires Node.js 22.12+
Odd-numbered Node versions (v23, etc.) are not supported. If your CI or server runs an older Node, you must upgrade before using Astro 6.
Content collections config file changed
Astro 5+ uses content.config.ts at the project root (not src/content/config.ts). The old location still works but is deprecated.
Common mistakes
Forgetting client:* on interactive components
<!-- BUG: renders to static HTML, no interactivity -->
<ReactCounter />
<!-- CORRECT: hydrates and becomes interactive -->
<ReactCounter client:load />
This is the most common Astro mistake. Without a hydration directive, framework components render once on the server and ship as static HTML.
Using className instead of class
Astro templates use standard HTML attributes, not JSX:
<!-- Wrong -->
<div className="container">
<!-- Correct -->
<div class="container">
Also: for not htmlFor, standard style="color: red" not style={{ color: 'red' }}.
Trying to use React hooks in .astro files
.astro components are server-only. No useState, useEffect, useRef, etc. If you need reactivity, use a client island or a <script> tag.
Importing Astro components in React
React components cannot import .astro components. The nesting goes one way only:
.astrocan use React, Svelte, Vue components- React/Svelte/Vue can only import their own framework components
Expecting automatic layouts
Unlike Next.js where layout.tsx wraps child routes automatically, Astro layouts must be explicitly imported and wrapped:
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<h1>My Page</h1>
</Layout>
There is no automatic layout nesting based on directory structure.
Missing getStaticPaths() for dynamic routes
In static mode, every [param].astro file needs getStaticPaths():
---
export async function getStaticPaths() {
return [
{ params: { slug: 'post-1' } },
{ params: { slug: 'post-2' } },
];
}
---
Without it, the build fails. If you want to skip this, use export const prerender = false (requires an adapter).
Adapter required for any on-demand route
Even one prerender = false route requires a server adapter. Without one, the build fails:
npx astro add node # or cloudflare, vercel, netlify
Expecting fetch() to cache like Next.js
Astro’s fetch() is standard fetch(). It does not cache, deduplicate, or revalidate like Next.js’s patched fetch(). If you want caching, use HTTP Cache-Control headers on your on-demand routes.
<style> is scoped by default
<style>
h1 { color: red; } /* Only affects h1 in this component */
</style>
If you want global styles, explicitly use is:global:
<style is:global>
h1 { color: red; } /* Affects all h1 elements */
</style>
This is the opposite of most frameworks where styles are global by default.
Build and deploy gotchas
Static endpoints produce files
A file like src/pages/feed.xml.ts with a GET export produces a feed.xml file at build time in static mode. The .ts extension is stripped. This is usually what you want, but it surprises people coming from Next.js where route handlers are always dynamic.
Images from src/ vs public/
src/assets/images: processed, optimized, fingerprintedpublic/images: copied as-is, no optimization
If you reference a public/ image with the <Image /> component, it will not be optimized. Use src/ imports for images you want optimized.
Environment variables
import.meta.env.PUBLIC_*- accessible in client codeimport.meta.env.*(without PUBLIC_) - server-only
This is different from Next.js (NEXT_PUBLIC_* prefix). If you forget the PUBLIC_ prefix, the variable is undefined in client code.
Experimental features (Astro 6)
These exist but should be used cautiously in production:
- Route caching - tag-based and path-based invalidation. Promising but not stable.
- Container API - render Astro components programmatically outside of pages.
Do not build critical production workflows on experimental features. Use explicit HTTP caching instead of route caching until it stabilizes.