I've just launched my first personal site. I almost built it in Next.js anyway.
Not from habit - I just knew Next.js by reputation. Huge community, shadcn/ui ecosystem, every tutorial assumes it. It's the gravitational default for anything frontend. I stopped myself, thought about what I actually needed, and picked as of April 2026 - Chrome and Edge, not yet Baseline - so I wouldn't reach for it in a product.
Content collections give typed frontmatter via Zod schemas. I write content.config.ts - twelve lines, shown below - drop an .mdx file in src/content/posts/, and it's typed end-to-end. No custom loader, no API route, no database client.
Shiki is the default code highlighter. Dual-theme light/dark takes one config block in astro.config.ts and a CSS media query - no PrismJS, no rehype chain, no copy-pasting highlight.js CSS from 2018. MDX integrates via @astrojs/mdx if I need React components inside a post.
What I'm giving up
Server actions - don't need them, the site has no mutations. Dynamic routes at request time - everything pre-renders at build, and Astro supports API routes if that changes. The cost is muscle memory: Astro's .astro syntax is new. First hour felt like JSX with amnesia. Hour two felt like HTML that finally got proper imports.
Where I'd still reach for Next.js
The admin dashboard, full-stack e-commerce, anything with real auth flows and a database - Next.js, no debate. Or Hono with a lighter frontend for API-first products. The question is always the same: where does the complexity live? If it's in content, pick a content framework. If it's in mutations and state, pick a full-stack framework.
"Just use Next.js for everything" is a reasonable default. It works, it ships, you don't have to think. The cost is that you stop matching the tool to the problem, and that atrophies judgment. Picking Astro for this site took 20 minutes. Those 20 minutes are the whole point.
// content.config.ts — the entire posts layer for this site.
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "zod";
const posts = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/posts" }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
summary: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { posts };
Twelve lines. Add a file, get types automatically.
SOCIAL SHARE CARD GENERATOR