next or astro for a static site

Next.js or Astro: which one to choose for a static site

In recent years, the “static site” paradigm (pre-rendering most pages at build time) has matured: frameworks now let you combine static pages with dynamic behaviors where needed. Next.js—once mainly for server-rendered React apps—now supports full static exports. Astro is a newer entrant built specifically around minimal client-side JavaScript and content-first sites.

If your site is mostly content (blog, docs, marketing pages), you’ll want minimal JavaScript overhead, fast page loads, and straightforward content workflows. If instead you expect interactive UIs, dynamic data, or API endpoints, you might lean toward more flexibility. My goal in this guide is to help you see clearly where Next.js and Astro shine or struggle, and then walk through how to build and deploy both approaches.

Rendering models and JavaScript payload

  • Next.js supports multiple rendering modes: static site generation (SSG), server-side rendering (SSR), client-side rendering (CSR), and incremental static regeneration (ISR).
  • Astro adopts a “static-first, zero-JavaScript-by-default” philosophy (sometimes called the “islands” architecture). That means pages are rendered to static HTML, and only interactive components get hydrated JavaScript.

Because Astro strips out unnecessary JS by default, its static builds often result in smaller payloads, faster Core Web Vitals, and less overhead on low-powered devices.

In Next.js, if you’re not careful, you may carry extra React hydration costs, but you get more flexibility to sprinkle in dynamic behaviors. Some parts of your site can remain static, while others (e.g. dashboards) remain dynamic.

Ecosystem, flexibility, and data handling

  • Next.js benefits from the massive React ecosystem. You can use React libraries, context hooks, server actions, and build full-stack features (API routes, serverless functions).
  • Astro supports multiple UI frameworks (React, Svelte, Vue, Solid, etc.) or plain HTML/JS. You’re not bound to one UI paradigm.
  • Content integration is smoother in Astro: Markdown, MDX, component slots, collections, etc., are first-class. For a blog or doc site, that can simplify your content pipeline.

Learning curve and developer ergonomics

Astro is generally lighter to pick up when your focus is content. You’ll spend less time managing hydration, routing quirks, or SSR subtleties. Next.js, with its layered rendering options, has more complexity to master, especially if you stray across SSR/SSG boundaries.

However, if you already know React well, Next.js may feel more familiar—and offer you more direct leverage of your existing skill set.

Use-case summary

  • Prefer Astro when your site is mostly static (blog, docs, marketing pages), and performance (minimal JS, fast loads) is a priority.
  • Prefer Next.js if you need mixing of static + dynamic content, API endpoints, user interaction, or you already have a React-based stack.

All that said: Next.js can be used for fully static sites (via the “export” mode). That means you can adopt Next.js now and still aim for static distribution.

Building a sample static site in Next.js (export mode)

Here’s a minimalist workflow to build a static Next.js site.

1. Bootstrap a Next.js project

npx create-next-app@latest my-site
cd my-site

Select TypeScript or JavaScript as you prefer. Use the App Router (if available), or the Pages Router—it works with static export.

2. Configure for static export

In next.config.js, set:

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  // optionally:
  // trailingSlash: true,
  // skipTrailingSlashRedirect: true,
};

module.exports = nextConfig;

This tells Next.js to render pages at build time and produce HTML + JS bundles.

3. Use getStaticProps / getStaticPaths

For pages that fetch data at build time:

// pages/posts/[slug].js

export async function getStaticPaths() {
  const posts = await fetchPosts();  // e.g. from CMS
  return {
    paths: posts.map(p => ({ params: { slug: p.slug } })),
    fallback: false,
  };
}

export async function getStaticProps({ params }) {
  const post = await fetchPost(params.slug);
  return { props: { post } };
}

export default function PostPage({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </article>
  );
}

The build will generate static HTML for each post.

4. Linking and routing

Use next/link for internal navigation:

import Link from 'next/link';

export default function Home({ posts }) {
  return (
    <ul>
      {posts.map(p => (
        <li key={p.slug}>
          <Link href={`/posts/${p.slug}`}>{p.title}</Link>
        </li>
      ))}
    </ul>
  );
}

Next.js prefetches linked pages by default when they enter the viewport, which makes navigation feel instant.

5. Build and export

npm run build
npm run export

This yields a out/ directory containing static HTML, CSS, and JS. You can host out/ on any static hosting (e.g. DigitalOcean App Platform, object storage + CDN, or a simple HTTP server).

6. Caveats and limitations

  • You can’t use features that require server runtime (e.g. API routes, server-side rendering) in export mode.
  • Dynamic behavior (sorting, filtering) needs client-side JS (React hooks) and must be built carefully.
  • Pay attention to hydration cost: if many components are interactive, you may undercut performance gains.

Building a sample static site in Astro

Now let’s walk the analogous setup with Astro.

1. Bootstrap an Astro project

npm create astro@latest my-astro-site
cd my-astro-site

Pick your preferences (TypeScript, framework integrations). Unlike Next.js, Astro encourages minimal setup by default.

2. Create pages and content

You can create pages under src/pages using .astro files or .md / .mdx for content.

Example: src/pages/index.astro

---
import { getCollection } from 'astro:content';
const posts = await getCollection('posts');
---

<html>
  <head><title>My Astro Site</title></head>
  <body>
    <h1>Blog</h1>
    <ul>
      {posts.map(post => (
        <li><a href={`/posts/${post.slug}/`}>{post.data.title}</a></li>
      ))}
    </ul>
  </body>
</html>

Then a post: src/content/posts/hello.md

---
title: "Hello from Astro"
slug: "hello"
---

This is my first post in Astro!

Astro’s content collections abstract content logic, making this clean.

3. Add interactivity selectively

If you want a React component inside a page, you import it and use a hydration directive:

---
// e.g. A React widget you wrote
import Counter from '../components/Counter.jsx';
---

<html>
  <body>
    <h1>Welcome</h1>
    <Counter client:load />
  </body>
</html>

Here, the Counter component will only hydrate (i.e. load JS) when needed. Default Astro output remains static HTML otherwise.

4. Build

npm run build

This outputs a dist/ folder ready to serve with static files. No additional “export” step needed.

5. Navigational behavior and prefetch

Astro doesn’t provide a custom Link component by default; you use standard <a> tags. To enable prefetching, you configure a setting or integrate a client-side script.

You lose built-in React-style prefetching, but this can be acceptable if your site is modest and content-first.

Deployment on DigitalOcean (static mode)

Here’s a sketch for how you might deploy either build on DigitalOcean:

  1. Create a Droplet (e.g. Ubuntu + nginx).
  2. Clone your site’s repository.
  3. Build on the server or push built static files.
  4. Serve via nginx (or Caddy) pointing to the out/ (Next) or dist/ (Astro) folder.
  5. Set up a CDN (e.g. DigitalOcean Spaces or Cloudflare) to front the static assets.

Because both Next.js in export mode and Astro produce pure static files, the deployment is identical in many respects. The key is your build pipeline (CI/CD) and caching strategies.

When to pick which: decision matrix

Need / ConstraintFavor AstroFavor Next.js (static)
Mostly content, few interactive parts
Need client-side interactivity or complex UI
Already use React and want to reuse code
Want minimal JS payload by default⚠ (possible, but more work)
Might evolve into dynamic (e.g. APIs) later⚠ (you’ll need separate backend)✅ (has built-in capabilities)
Familiarity or team skill✅ (easier)✅ (if team knows React)

One piece of advice: if your initial version is content-only, go with Astro. You can always introduce a dynamic backend later. If instead you anticipate dynamic needs from the start—or want to reuse React logic—Next.js offers future flexibility.

Was this helpful?

Thanks for your feedback!

Leave a comment

Your email address will not be published. Required fields are marked *