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) with prerender = false on specific routes
  • output: 'server' with prerender = true on 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:

  • .astro can 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, fingerprinted
  • public/ 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 code
  • import.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.