Skip to content

Headless & API

Astro blog: how to build one, Astro vs Next.js, and the SEO checklist

Astro blogs are blogs built with Astro, a JavaScript framework for content-driven websites that renders pages to HTML on the server, at build time by default, and sends no JavaScript to the browser unless a component asks for it. Posts come from Markdown or MDX files in the project, or from a CMS or API during the build.

Last reviewed October 12, 2026 · 7 min read

Reviewed by Yurii, Founder of Structura

Astro's documentation describes the framework as "designed to showcase your content and to allow you to opt-in to interactivity only as needed." A blog is mostly text, images and links, which is what Astro outputs best, and its content tooling covers the jobs a blog repeats, from validating each post's front matter to generating the sitemap.

Why Astro suits a blog

No JavaScript unless you ask for it. "By default, Astro will automatically render every UI component to just HTML & CSS, stripping out all client-side JavaScript automatically." A comment widget or a newsletter form becomes an interactive island with a client:* directive, and client:visible loads it only once it scrolls into view. The rest of the post stays plain HTML.

Static by default, dynamic where needed. Pages are prerendered at build time unless you switch a route to on-demand rendering with a server adapter, so without an adapter Astro works as a static site generator. In the words of Astro's migration guide, "Astro projects can also be SSG or SSR with page-level prerendering." Static vs dynamic websites explains what each mode means for a blog.

Typed content. Posts form a content collection with a schema, and "if any file violates its collection schema, Astro will provide a helpful error." A post missing its description fails the build instead of shipping without a meta description.

How to build an Astro blog

  1. Start from the blog starter. npm create astro@latest -- --template blog creates a working project from Astro's official blog starter, which loads Markdown and MDX posts with the built-in glob() loader and a collection schema.
  2. Define the posts collection. In src/content.config.ts, point a glob() loader at your posts folder and give the collection a schema: title, description, publish date, updated date and hero image.
  3. Create the routes. A dynamic page such as src/pages/blog/[...slug].astro exports getStaticPaths(), which returns one entry per post from getCollection(). In static mode, "all routes must be determined at build time," so Astro writes one HTML file per post. An index page lists the collection sorted by date.
  4. Write the shared head. A layout component outputs the title, meta description, canonical link and Open Graph tags from each post's fields.
  5. Set the site URL and add the integrations. Set site in astro.config.mjs, add @astrojs/sitemap, and add an RSS feed with @astrojs/rss.
  6. Deploy. The build output goes to any static host. Add an adapter only if some routes render on demand.

Pulling posts from an API instead of Markdown

Content collections are not limited to files. A collection's entries can be "fetched from remote sources such as a database, CMS, or API endpoint," and the simplest loader is an async function that returns an array of entries, each with a unique id:

// src/content.config.ts
import { defineCollection } from "astro:content";

const blog = defineCollection({
  loader: async () => {
    const res = await fetch(`${import.meta.env.CONTENT_API}/posts`);
    const { posts } = await res.json();
    return posts.map((post) => ({ id: post.slug, ...post }));
  },
});

export const collections = { blog };

Remote posts then get the same getCollection() queries and schema validation as local Markdown. Two choices follow:

  • Build-time collections fetch once per build and store the result. Astro suggests "using build-time content collections whenever possible": they are the fastest and support MDX and image optimization, but a new post appears only after the next build.
  • Live collections fetch on every request, so posts appear without a rebuild, at the cost of a fetch per page view and no MDX or image optimization. They pay off on routes rendered on demand. A middle path is on-demand rendering behind a cache: the Vercel adapter, for example, has an isr option that caches rendered pages after the first request.

An Astro SEO checklist

  • Set site. Astro uses your deployed URL for canonical URLs, and the sitemap integration "needs to know your site's deployed URL to generate a sitemap."
  • Write the head yourself. Astro has no metadata API like Next.js. One shared layout should output the title, meta description, a self-referencing canonical on your public domain and Open Graph tags, from fields your collection schema makes required.
  • Generate the sitemap. @astrojs/sitemap "will crawl your statically-generated routes and create the sitemap file, including dynamic routes like [...slug]," but it "cannot generate sitemap entries for dynamic routes in SSR mode." For blog routes rendered on demand, add the URLs through its customPages option or write your own sitemap endpoint.
  • Publish RSS. @astrojs/rss generates a feed from the collection, at build time or on demand.
  • Emit JSON-LD. Add a script tag of type application/ld+json with a BlogPosting node to the post layout.
  • Do redirects at the host. Astro's redirects config defaults to a 301, but on a static build "Astro will output HTML files with the meta refresh tag by default," and "the status code is not used by the server." Search engines get a real 301 only from an adapter that writes the host's redirect rules, or from redirects you set on the host yourself.
  • Return real 404s. Check that unknown URLs get a 404 status from your host, not a 200 with an error page.

The full list for any API-fed front end is in headless CMS SEO.

Astro vs Next.js for a blog

Astro Next.js
Model Multi-page site; components render to HTML React application; pages prerender, then hydrate in the browser
JavaScript sent by default None; islands opt in React and its router, plus your client components
Rendering Static by default; on demand per route with an adapter Static, ISR or per request, per route
Regenerating pages without a full rebuild Through adapters, such as Vercel's ISR option Built in, with time-based and on-demand revalidation
UI components React, Vue, Svelte, Solid and others, mixable React
SEO tooling Head written in a layout; official sitemap and RSS integrations Metadata API plus sitemap and robots files built in
Content Content collections with loaders and schemas No content layer; fetch data or use MDX

Astro's migration guide frames the core difference: "Next.js is a React single-page app," while "Astro is a multi-page site." Choose Astro when the site is mostly content and you want the least JavaScript and the simplest build. Choose Next.js when the blog sits inside a React application, needs per-request personalization, or relies on incremental regeneration across thousands of pages. Neither ranks better by itself: both send server-rendered HTML, and the SEO difference comes from how completely each site fills in the checklist. The Next.js SEO page covers that framework's side of the list, and the Next.js CMS comparison applies to either framework when you pick a content source.

How Structura handles this

Structura's Content API is framework-agnostic: JSON over HTTPS with no SDK, so an Astro site reads it the same way the documented Next.js example does. In a build-time collection loader, page through GET /posts (up to 50 posts per page, following nextCursor), then fetch GET /posts/{slug} for each post, because the list returns summary fields only. Each post brings sanitized html with links pointing at your public domain, metaTitle, metaDescription, og, featured and body images with width, height and alt text, categories and tags (with a /terms endpoint for archive pages), and a schema array of JSON-LD with a BlogPosting node and the author as a Person. Use the slug as the entry ID and updatedAt to see what changed.

Structura does not trigger your build, so a static Astro blog shows a newly published post after its next build. Schedule builds to match the campaign's cadence or, on paid plans, use article delivery to send each published post to an HTTPS endpoint you build, which can store it and call your host's deploy hook. If you keep WordPress as the backend instead, the plugin's headless mode publishes native posts there and points their links at your Astro site. The API-first CMS page lists the endpoints and response fields.

FAQ

Is Astro good for a blog?

Yes. Astro was built for content sites: posts render to plain HTML, no JavaScript is sent unless a component needs it, and content collections validate every post's front matter at build time. The official blog starter gives you a working blog in one command. It fits less well when the blog lives inside a large React application, where Next.js shares the codebase.

Is Astro better than Next.js for SEO?

Neither framework ranks better by itself. Both send server-rendered HTML that search engines can read without running JavaScript. Astro ships less JavaScript by default, which makes fast pages easier to get; Next.js has more SEO tooling built in, such as its Metadata API and its sitemap and robots files. The site that fills in titles, canonicals, sitemaps, structured data and status codes more completely does better.

Can Astro pull posts from a headless CMS?

Yes. A content collection can use a loader that fetches from a CMS or API at build time, or a live collection that fetches on each request for routes rendered on demand. Astro's integrations directory lists community-built and third-party loaders, and for a simple JSON API an async function that returns an array of entries is enough.

Does Astro generate a sitemap and RSS feed?

Not out of the box, but official integrations do. The @astrojs/sitemap integration builds a sitemap from your prerendered routes once you set the site URL, and @astrojs/rss generates a feed from your posts.

Share this post