Toolmingo
Guides16 min read

Next.js vs Remix vs Astro: Which Framework in 2025?

A deep-dive comparison of Next.js, Remix, and Astro — covering rendering strategies, performance, routing, ecosystem, and which meta-framework to pick for your next project.

Three frameworks, three philosophies. Next.js is the React full-stack powerhouse. Remix (now React Router v7) is the progressive-enhancement idealist. Astro is the content-site speed champion that ships zero JavaScript by default. This guide breaks down exactly when to use each.

At a glance

Next.js 15 Remix / React Router v7 Astro 5
Released 2016 2021 (Remix), 2024 (RR v7) 2021
Primary use case Full-stack React apps, e-commerce, SaaS Data-driven apps, forms, progressive enhancement Content sites, blogs, marketing, documentation
UI framework React only React only Any (React, Vue, Svelte, Solid, or none)
Default rendering SSR + RSC (App Router) SSR SSG / MPA
JS shipped by default Runtime JS bundle Runtime JS bundle ~0 KB (islands on demand)
Routing File-system (app/ or pages/) File-system (nested layouts) File-system (src/pages/)
Data fetching fetch() in RSC, Server Actions loader/action functions Astro.props, Content Collections, loaders
Backend Route Handlers, Server Actions Remix loaders/actions Endpoints (.ts files)
Learning curve Medium → High (App Router) Medium Low → Medium
Backed by Vercel Shopify (open source) The Astro team
GitHub stars ~130k ~30k (Remix) ~50k

How each framework works

Next.js: The full-stack Swiss Army knife

Next.js wraps React with a file-system router, two rendering modes (App Router with React Server Components, and the legacy Pages Router), and a suite of server features. The App Router (default since Next.js 13) treats components as server-rendered by default — they run on the server, access databases directly, and send HTML+RSC payload to the client.

// app/posts/[slug]/page.tsx — Server Component (no 'use client')
import { db } from '@/lib/db'

export default async function PostPage({ params }: { params: { slug: string } }) {
  // Direct DB access — runs on the server only
  const post = await db.post.findUnique({ where: { slug: params.slug } })
  return <article>{post?.content}</article>
}

// Server Action — form submitted without a separate API endpoint
async function likePost(formData: FormData) {
  'use server'
  const postId = formData.get('postId') as string
  await db.post.update({ where: { id: postId }, data: { likes: { increment: 1 } } })
}

Next.js strengths:

  • Largest ecosystem (most tutorials, plugins, community answers)
  • Native Vercel deployment (zero-config Edge, ISR, image optimisation)
  • React Server Components — colocate DB queries with UI
  • next/image, next/font, next/link for performance
  • ISR (Incremental Static Regeneration) — pages rebuild in background

Remix / React Router v7: Progressive enhancement done right

Remix merged with React Router in late 2024. React Router v7 (previously Remix v2) introduces "framework mode" — you get Remix's loader/action model directly in the React Router package. The philosophy: forms work without JavaScript, and you build on top of web standards (Request/Response, FormData, URLSearchParams).

// app/routes/posts.$slug.tsx — loader fetches, action handles mutations
import { data, Form, useLoaderData } from 'react-router'
import { db } from '~/lib/db'
import type { Route } from './+types/posts.$slug'

export async function loader({ params }: Route.LoaderArgs) {
  const post = await db.post.findUnique({ where: { slug: params.slug } })
  if (!post) throw data('Not Found', { status: 404 })
  return { post }
}

export async function action({ request, params }: Route.ActionArgs) {
  const formData = await request.formData()
  await db.post.update({
    where: { slug: params.slug },
    data: { likes: { increment: 1 } },
  })
  return { ok: true }
}

export default function PostRoute({ loaderData }: Route.ComponentProps) {
  const { post } = loaderData
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {/* Works without JavaScript — native form */}
      <Form method="post">
        <input type="hidden" name="intent" value="like" />
        <button type="submit">Like</button>
      </Form>
    </article>
  )
}

Remix strengths:

  • Progressive enhancement — works without JS, enhanced with JS
  • Nested routes with independent loaders (no waterfall fetching)
  • Full-stack mutations via actions (no separate API layer)
  • Automatic error boundaries at route level
  • Runs on any runtime: Node.js, Cloudflare Workers, Deno, Bun

Astro: Ship less JavaScript, rank higher

Astro is an MPA (Multi-Page App) framework with an islands architecture. Pages are .astro files (HTML-like templates with frontmatter scripts) that render to static HTML by default. JavaScript is opt-in via client:* directives on interactive components — from any UI framework.

---
// src/pages/posts/[slug].astro — runs at build time (SSG) or request time (SSR)
import { getEntry, render } from 'astro:content'
import Layout from '@/layouts/BaseLayout.astro'

const { slug } = Astro.params
const post = await getEntry('blog', slug!)
const { Content } = await render(post)
---

<Layout title={post.data.title}>
  <article>
    <h1>{post.data.title}</h1>
    <Content />
  </article>
  <!-- Interactive island — loads React only for this component -->
  <LikeButton client:load postId={post.id} />
</Layout>
---
// Using multiple UI frameworks in one project
import ReactCounter from '@/components/Counter.tsx'   // React
import VueSearch from '@/components/Search.vue'        // Vue
import SvelteChart from '@/components/Chart.svelte'    // Svelte
---

<!-- Each island is independently hydrated -->
<ReactCounter client:visible />
<VueSearch client:idle />
<SvelteChart client:media="(min-width: 768px)" />

Astro strengths:

  • Zero JS by default — perfect Lighthouse scores on content pages
  • Framework-agnostic — use React, Vue, Svelte, or plain HTML
  • Content Collections with type-safe frontmatter
  • View Transitions API for SPA-like navigation
  • Built-in Markdown + MDX support

Rendering strategies compared

Strategy Next.js Remix / RR v7 Astro
SSG (build time) generateStaticParams + no dynamic ssr: false adapter mode Default (no adapter)
SSR (per request) Default in App Router Default With @astrojs/node or Cloudflare adapter
ISR revalidate in fetch / next.revalidate ❌ (not built-in) Experimental server:defer
RSC (React Server Components) ✅ App Router
Streaming ✅ Suspense boundaries defer() in loaders ✅ Experimental
Edge rendering runtime: 'edge' ✅ Cloudflare Workers ✅ Any adapter
Partial hydration ✅ Islands (client:*)

Routing and nested layouts

All three use file-system routing, but with different conventions:

Feature Next.js Remix / RR v7 Astro
Convention app/ (App Router) or pages/ app/routes/ src/pages/
Dynamic segment [slug] $slug [slug]
Catch-all [...slug] $ (splat) [...slug]
Nested layouts layout.tsx per folder Dot-separator routes Layout components (manual)
Parallel routes @folder slots
Intercepting routes (.)route
Loading state loading.tsx useNavigation() ❌ (full page)
Error boundary error.tsx ErrorBoundary export ❌ (manual)
# Next.js App Router structure
app/
  layout.tsx          ← root layout
  page.tsx            ← /
  posts/
    layout.tsx        ← wraps all /posts/* routes
    page.tsx          ← /posts
    [slug]/
      page.tsx        ← /posts/my-post
      loading.tsx     ← skeleton while fetching
      error.tsx       ← error boundary

# Remix / React Router v7 structure
app/routes/
  _index.tsx          ← /
  posts._index.tsx    ← /posts  (dot = nested)
  posts.$slug.tsx     ← /posts/my-post

# Astro structure
src/pages/
  index.astro         ← /
  posts/
    index.astro       ← /posts
    [slug].astro      ← /posts/my-post

Data fetching patterns

// Next.js — Server Component with direct DB access
async function PostList() {
  const posts = await db.post.findMany({ orderBy: { date: 'desc' } })
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}

// Next.js — cached fetch with revalidation
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 }, // ISR: rebuild every hour
  })
  return res.json()
}

// Server Action — client mutation without API route
'use server'
async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  await db.post.create({ data: { title } })
  revalidatePath('/posts')
}
// Remix / React Router v7 — loader runs before render
export async function loader({ request }: Route.LoaderArgs) {
  const url = new URL(request.url)
  const page = Number(url.searchParams.get('page') ?? 1)
  const posts = await db.post.findMany({ skip: (page - 1) * 10, take: 10 })
  return { posts, page }
}

// Parallel data fetching — no waterfall
export async function loader() {
  const [posts, categories] = await Promise.all([
    db.post.findMany(),
    db.category.findMany(),
  ])
  return { posts, categories }
}

// Deferred (stream expensive data)
import { defer } from 'react-router'
export async function loader() {
  return defer({
    posts: db.post.findMany(),          // awaited immediately
    analytics: getSlowAnalytics(),      // streamed in background
  })
}
---
// Astro — frontmatter runs on server (build or request time)
import { getCollection } from 'astro:content'

const posts = await getCollection('blog', ({ data }) => !data.draft)
const sorted = posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
---

{sorted.map(post => (
  <article>
    <a href={`/posts/${post.slug}`}>{post.data.title}</a>
    <time>{post.data.date.toLocaleDateString()}</time>
  </article>
))}

Performance compared

Metric Next.js Remix / RR v7 Astro
Initial HTML Server-rendered (SSR/RSC) Server-rendered Server-rendered
JS bundle (hello world) ~90 KB (React + framework) ~75 KB (React + RR) ~0 KB
JS bundle (real app) 150–400 KB 120–300 KB 10–60 KB (islands only)
Time to Interactive Medium (hydration cost) Medium (hydration cost) Fast (minimal hydration)
Core Web Vitals (content) Good Good Excellent
Core Web Vitals (SaaS) Good Good Good (if using React islands)
Image optimisation next/image built-in Manual (via adapter) <Image /> built-in
Font optimisation next/font Manual @astrojs/font

Real-world bundle audit (blog homepage):

  • Next.js App Router: ~94 KB JS (gzip)
  • Remix: ~72 KB JS (gzip)
  • Astro (no islands): ~4 KB JS (gzip)
  • Astro (React island for search): ~52 KB JS (gzip, loads lazily)

Ecosystem and integrations

Next.js ecosystem

Category Options
Auth NextAuth.js (Auth.js), Clerk, Supabase Auth, Lucia
Database ORM Prisma, Drizzle, Supabase, Mongoose
UI libraries shadcn/ui, Radix UI, Chakra, MUI, Ant Design
State management Zustand, Jotai, Redux Toolkit, TanStack Query
Styling Tailwind CSS, CSS Modules, styled-components, Stitches
Testing Vitest, Jest, Playwright, Cypress, Testing Library
CMS Sanity, Contentful, Payload, Strapi, Builder.io
Deploy Vercel (native), AWS, Railway, Fly.io, Render
E-commerce Medusa, Commerce.js, Shopify Hydrogen (moved to Remix)
Analytics Vercel Analytics, PostHog, Plausible

Remix / React Router v7 ecosystem

Category Options
Auth Remix Auth, better-auth, Clerk
Database ORM Prisma, Drizzle (great fit for Cloudflare D1)
UI libraries Any React UI library
Styling Tailwind CSS, Vanilla Extract, CSS Modules
Deploy Cloudflare (Workers/Pages), Fly.io, Vercel, Railway
Testing Vitest + Testing Library, Playwright
Community Smaller than Next.js; strong Cloudflare community

Astro ecosystem

Category Options
UI frameworks React, Vue, Svelte, Solid, Preact, Alpine.js, Lit
Content Markdown, MDX, Content Collections (built-in)
CMS Storyblok, Sanity, Contentful, Ghost, Decap
Styling Tailwind CSS, CSS Modules, scoped <style> per component
Database Any (via API routes or Astro DB — built-in SQLite/libSQL)
Deploy Cloudflare Pages/Workers, Vercel, Netlify, Node.js
Testing Playwright (E2E), Vitest for utils
Themes Rich official + community theme ecosystem
Integrations 100+ official integrations (npx astro add tailwind)

Where each framework wins

Next.js wins for

Scenario Why
SaaS dashboards RSC + Server Actions = minimal API boilerplate
E-commerce ISR + next/image + Commerce integrations
Large teams Biggest ecosystem, most StackOverflow answers
Vercel-deployed apps Zero-config ISR, Edge Middleware, Analytics
AI/LLM apps AI SDK, streaming responses, Vercel AI integrations
When you want maximum options Pages Router or App Router, RSC or not
SEO-critical apps RSC + ISR + automatic metadata API

Remix / React Router v7 wins for

Scenario Why
Complex forms and mutations Loader/action mental model reduces boilerplate
Progressive enhancement required Works without JS — government, accessibility-first sites
Cloudflare Workers Best-in-class Worker adapter
Nested layouts with isolated data Each route loads its own data independently
Existing React Router v6 apps Migration path via framework mode
Clean HTTP semantics Respects Cache-Control, status codes, web standards
Real-time feedback (optimistic UI) useFetcher + optimistic updates built in

Astro wins for

Scenario Why
Blogs and documentation Zero JS, perfect Core Web Vitals
Marketing / landing pages Fast by default, great SEO
Multi-framework teams React devs + Vue devs can coexist
Content-heavy sites Content Collections = type-safe MDX management
Portfolio sites Beautiful theme ecosystem, fast builds
When you need to migrate incrementally Islands let you add React bit by bit
Lighthouse score requirements 100/100 achievable without effort

Full comparison table

Feature Next.js 15 Remix / RR v7 Astro 5
React required Optional
TypeScript support Excellent Excellent Excellent
RSC (React Server Components)
SSR ✅ (with adapter)
SSG ✅ (default)
ISR Experimental
Zero JS by default
Island architecture
Nested layouts ✅ (layout.tsx) ✅ (dot routes) Manual
Built-in form handling Server Actions loader/action Endpoints
File uploads Server Actions action + FormData Endpoints
Middleware ✅ (Edge)
Image optimisation ✅ next/image Manual
Font optimisation ✅ next/font Manual
Built-in CSS Modules
Tailwind support
MDX support Plugin Plugin ✅ built-in
Content Collections ✅ built-in
View Transitions Manual Manual ✅ built-in
API routes Route Handlers Resource routes Endpoints
WebSocket support Limited Cloudflare Durable Limited
Cloudflare Workers ✅ (via adapter) ✅ (best support)
Edge runtime
Storybook support
Docker deploy
Standalone output output: standalone
Bundle analyser @next/bundle-analyzer Manual Manual
DevTools Remix DevTools
Stable API
Community size ★★★★★ ★★★ ★★★★
Job market ★★★★★ ★★★ ★★★

Migration paths

From → To Effort Notes
Pages Router → App Router (Next.js) Medium Incremental — both coexist
React Router v6 → React Router v7 (framework mode) Low–Medium Official migration guide
CRA / Vite → Next.js Medium Add file-system routing, adjust data fetching
Next.js Pages Router → Astro Medium Replace getStaticProps with frontmatter fetching
Next.js App Router → Astro High Different data model (RSC → islands)
Gatsby → Astro Low Both are SSG-first; GraphQL layer removed
WordPress → Astro Low–Medium Astro + headless WP via REST or WPGraphQL

Decision guide

Do you need React specifically?
  ├── No → Astro (use any framework or none)
  └── Yes →
      Is this a content/marketing/blog site?
        ├── Yes → Astro (React islands where needed)
        └── No (app / SaaS / e-commerce) →
            Do you care about progressive enhancement / Cloudflare?
              ├── Yes → Remix / React Router v7
              └── No → Next.js

Still unsure?
- Largest team / most hiring? → Next.js
- Best SEO with minimal JS? → Astro
- Web standards / edge-first? → Remix / React Router v7

Common mistakes

Mistake Framework Fix
Adding 'use client' to every component Next.js Only client-ify leaf components that need browser APIs or events
Fetching in useEffect instead of loaders Remix Move fetching to loader — that's the whole point
Installing React in an Astro project when plain HTML/CSS suffices Astro Use .astro components; only add React for interactive islands
Using getServerSideProps in App Router Next.js App Router = async Server Components; no getServerSideProps
Using <Link> from react-router in framework mode without the adapter Remix / RR v7 Import from react-router, not react-router-dom
Shipping a 300 KB React bundle for a static blog Astro Astro = 0 KB JS for static content
Forgetting that loader runs on every navigation Remix Use caching headers (Cache-Control) if loader is slow
Not configuring output: 'standalone' for Docker Next.js Required to avoid shipping node_modules in Docker image

Next.js vs Remix vs Astro vs SvelteKit

Next.js Remix / RR v7 Astro SvelteKit
UI language React / TSX React / TSX Any / .astro Svelte
Bundle size Large Large Tiny Small
RSC support
Zero JS
Form mutations Server Actions loader/action Endpoints form actions
Deploy anywhere
Community ★★★★★ ★★★ ★★★★ ★★★★
Best for Full-stack React Progressive web apps Content sites Svelte full-stack

FAQ

Q: Is Remix dead now that it merged with React Router?

No. Remix v2 features are now in React Router v7 under the "framework mode" (react-router.config.ts). The Remix team works on React Router v7. Existing Remix v2 apps migrate with minimal changes — mostly updating import paths. The brand "Remix" is being sunset in favour of "React Router", but the technology is very much alive and actively developed.


Q: Can I use Astro for a full SaaS app (not just content)?

Yes, but you'll write most of the app as interactive React/Vue/Svelte islands, which reduces Astro's main advantage (zero JS). For a dashboard or SaaS app with lots of client interactivity, Next.js or Remix will feel more natural. Astro's sweet spot is 80%+ static content with sprinkles of interactivity.


Q: Next.js App Router vs Pages Router — which should I use in 2025?

App Router for new projects. It's the Vercel-recommended default with React Server Components, built-in streaming, and Server Actions. Pages Router is stable and won't be removed, but receives minimal new features. The App Router learning curve is steeper (understanding RSC boundaries), but worth it for new projects.


Q: Which is best for SEO?

All three render HTML server-side, so search crawlers see content immediately. Astro ships the least JavaScript, which helps Core Web Vitals (especially INP and TBT). Next.js with ISR serves pre-built HTML instantly. In practice, any of the three will give you excellent SEO if you use their SSR/SSG modes correctly.


Q: Which framework deploys best to Cloudflare?

Remix / React Router v7 has the best Cloudflare Workers/Pages integration — it was designed with edge runtimes in mind and Cloudflare is a first-class deployment target. Astro also has an excellent @astrojs/cloudflare adapter. Next.js supports Cloudflare via the @opennextjs/cloudflare community adapter (not official).


Q: Should I learn Next.js, Remix, or Astro first?

If you're job hunting: Next.js (most jobs by far). If you're building a personal blog or marketing site: Astro (fastest and simplest). If you want to understand web fundamentals (HTTP, forms, progressive enhancement): Remix / React Router v7 (teaches you to think in web standards). All three are worth knowing — they solve different problems well.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools