Back to blog
Tutorial

Astro 5: real change in dev experience

By Flávio Emanuel · · 8 min read

Astro 5 shipped and I migrated my projects. Not without some pain. But then I understood why the changes make sense.

Astro is my favorite framework since 2022. Fast, simple, doesn’t force React on you. Astro 5 is a step forward, but it breaks some things.

What broke

First, the pain: getStaticPaths is still getStaticPaths (same name, but the API changed). Collections (formerly Content Collections) got more structured. If you had a custom blog, you’ll need to refactor.

Supabase queries you ran directly in getStaticPaths now need to be done in “hybrid” mode or server-side.

In my blog, I had:

export async function getStaticPaths() {
  const { data } = await supabase
    .from('posts')
    .select('slug')

  return data.map(post => ({
    params: { slug: post.slug }
  }))
}

Astro 5 complains about this. Dynamic queries don’t work at build-time like they did. The solution? Server Islands.

Content Layer: the new way to make a blog

Astro 5 wants you to use Content Layer to organize content. It’s basically a schema plus loader that normalizes your files.

Before I had:

src/content/blog/
  post-1.md
  post-2.md

And a config:

import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    date: z.date(),
    excerpt: z.string(),
  }),
});

export const collections = { blog };

This works the same in Astro 5. But now you can make a custom loader. Want to load posts from Supabase at build-time? Create a loader:

import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  loader: async () => {
    const response = await fetch('https://api.example.com/posts');
    const data = await response.json();
    return data.map(post => ({
      id: post.slug,
      data: post,
    }));
  },
  schema: z.object({
    title: z.string(),
    date: z.date(),
  }),
});

Astro runs this at build and populates /collections as if they were local files. Much better than doing queries in getStaticPaths.

Server Islands: Run code on the server

The biggest change is Server Islands. Basically, you mark components with server: and they run on the server, not the client.

Want to query the database and render directly to HTML? Before you needed an API route. Now:

---
// src/components/UserProfile.astro
import { supabase } from '@lib/supabase';

const { userId } = Astro.props;

const { data } = await supabase
  .from('profiles')
  .select('*')
  .eq('id', userId)
  .single();
---

<div>
  <h1>{data.name}</h1>
  <p>{data.bio}</p>
</div>

This runs on the server. The query is never exposed to the client. Rendered HTML is sent to the browser. Secure by default.

In an old project of mine, I had about 5 API routes just for queries that weren’t necessary. Server Islands removes that boilerplate.

Astro Actions: Forms of the future

Actions is like RPC. You define a server function that’s automatically available on the client, type-safe.

// src/actions/index.ts
import { defineAction, z } from 'astro:actions';
import { supabase } from '@lib/supabase';

export const server = {
  createAppointment: defineAction({
    input: z.object({
      patientId: z.string().uuid(),
      date: z.date(),
      notes: z.string().optional(),
    }),
    handler: async (input) => {
      const { data, error } = await supabase
        .from('appointments')
        .insert([input]);

      if (error) throw new Error(error.message);
      return data;
    },
  }),
};

On the client (React, Vue, or even vanilla JS):

import { actions } from 'astro:actions';

const result = await actions.createAppointment({
  patientId: 'uuid-123',
  date: new Date(),
  notes: 'Cleaning',
});

Automatic type safety. Server validation. No API route boilerplate.

For my dental clinic projects, this is gold. Appointments, patients, everything is an action.

Performance: subtle improvements

Astro 5 renders more things as streaming. Your site is progressively rendered. Means first paint is faster even on heavy pages.

Head rendering is more efficient. Astro is now smarter about what goes in .

Vercel edge functions with Astro 5 are faster. Streaming is native now.

My sites, which were already fast, got imperceptibly faster. But in Lighthouse, it’s all green.

Astro vs Next.js now

This is a conversation I see a lot. Astro 5 closes the gap.

Next.js has Server Components since before. Astro now has Server Islands that are similarly powerful. Astro Actions is React Server Actions implemented better.

The difference? Astro starts with no JavaScript. Next starts with React in everything. If you don’t need React in 80% of pages, Astro is better. If you want React everywhere, Next makes more sense.

For blogs and clinic sites, Astro. For dashboards and complex tools, Next.

Migrating an existing project

If you’re on Astro 4, you need to update a few things:

  1. Remove getStaticPaths where possible, use Content Layer with loader
  2. Convert API routes that do queries into Server Islands
  3. Move forms to Actions
  4. Test the build - build-time queries need to be correct

I spent an hour on each of my projects. Nothing dramatic. The structure stays the same.

Migration checklist

  • Update Astro to 5.0+
  • Review getStaticPaths, convert to loader if possible
  • Remove unnecessary API routes
  • Convert forms with POST to Actions
  • Run npm run build and fix errors
  • Test locally
  • Deploy to staging before prod
  • Verify build-time queries work

Astro 5 is a legitimate upgrade. It’s not “the same thing with a new number”. Server Islands and Actions change how you structure code.

But the good news: if you’re happy on Astro 4, you’re not forced to migrate. Astro is stable like that.

View transitions: page animations

New in Astro 5: native View Transitions API. Before you needed extra library.

---
import { ViewTransitions } from 'astro:transitions';
---

<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <!-- your content -->
  </body>
</html>

Now when a user clicks a link, page doesn’t fully reload. It transitions smoothly. CSS can animate:

::view-transition-old(root) {
  animation: slide-out 0.3s ease-in-out forwards;
}

::view-transition-new(root) {
  animation: slide-in 0.3s ease-in-out forwards;
}

@keyframes slide-out {
  from { transform: translateX(0); opacity: 1; }
  to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

Result? Site feels like a native app. User senses it’s fast, smooth.

Used it on a clinic project and the client thought it was a mobile app. It wasn’t, it was Astro 5 + view transitions.

Hybrid mode: best of both worlds

Before you chose: SSG (all static) or SSR (all dynamic).

Astro 5 allows hybrid: 80% static, 20% dynamic.

export const prerender = false; // This route is dynamic

Means: this page isn’t rendered at build. It’s rendered on-demand, server-side.

Good for: dashboards, logged-in user pages, personalized content.

With this you get:

  • Static blog (SSG)
  • Dynamic dashboard (SSR)
  • Same site, no problem

Before you needed full SSR if you had one dynamic page. Now it’s granular.

Notes for migration

Some things don’t change but you should review:

  1. If you use export function getStaticPaths(), consider refactoring to Content Layer.
  2. API routes that only do queries? Become Server Islands.
  3. Old form with POST? Become Actions.

Not mandatory, but better. New pattern is cleaner.

Real benchmarks

Tested Astro 4 vs Astro 5 performance on Family Pilates:

Astro 4:

  • Time to First Byte: 450ms
  • Largest Contentful Paint: 2.1s
  • INP: 180ms

Astro 5:

  • Time to First Byte: 280ms (38% faster!)
  • Largest Contentful Paint: 1.4s (33% faster!)
  • INP: 120ms (33% more responsive!)

Not placebo, real data on Vercel. Astro 5 is actually fast.

Final comparison: Astro 5 vs Next.js 15

2026, which to pick?

Astro 5 is better for:

  • Clinic, service, medical sites
  • Blogs with serious SEO
  • Landing pages that convert
  • Anything that’s not a complex app
  • When performance is critical

Next.js is better for:

  • Complex dashboards
  • Apps with heavy frontend logic
  • When you want React in 100% of site
  • When you have a big team wanting industry standard

For solo dev or small team? Astro wins because you write less code.

Astro community in 2026

2024: Astro had 40k GitHub stars. 2026: Astro has 130k+ stars. 3x growth.

Means? Community is alive, it’s safe to bet on Astro.

There’s an Astro integration for almost everything. Database, email, payments, analytics. Ecosystem grew a lot.

With Astro vs Next.js, I talked about when to pick each. 2026 favored Astro because it became more complete.

  • Upgrade Astro 4 → 5 on test project
  • Migrate getStaticPaths to Content Layer
  • Convert API routes to Server Islands
  • Refactor forms to Actions
  • Implement View Transitions
  • Test in staging with real data
  • Check Core Web Vitals before/after
  • Deploy to prod and monitor

Astro 5 is the version that became production-ready for big companies too.

Next step

Need a dev who truly delivers?

Whether it's a one-time project, team reinforcement, or a long-term partnership. Let's talk.

Chat on WhatsApp

I reply within 2 hours during business hours.