After four failed attempts to build a portfolio site over three years—using React, Next.js, and Gatsby—I finally shipped one with Astro. Each previous attempt followed the same pattern: start strong, get 80% done, realize I was over-engineering, and abandon the project. The portfolio became a graveyard of unfinished rebuilds.
Astro broke this cycle not because it’s perfect, but because it removes the obstacles that typically derail personal projects. There’s no client-side hydration to optimize, no bundle splitting to configure, and no JavaScript unless explicitly requested. The focus remains on building and deploying.
Architectural Simplicity
Astro’s architecture eliminates the mental overhead of server vs. client code execution. Everything renders on the server by default, with JavaScript as an opt-in rather than default feature. This fundamental shift in priorities prevents common performance pitfalls and keeps projects focused on content delivery rather than framework optimization.
Component Design
Astro components work as enhanced HTML files with TypeScript capabilities. There’s no JSX syntax to learn or virtual DOM to manage. Components import in the frontmatter and render as standard HTML, making them immediately familiar to anyone with web development experience.
For example, a simple Divider component imports props and assets in the frontmatter section, then renders standard HTML with class attributes for styling. This approach maintains the simplicity of web development while adding component reusability and asset optimization through Astro’s built-in Image component.
// src/components/Divider.astro
---
const { Class } = Astro.props;
import { Image } from "astro:assets";
import sadKirblet from "../images/svg/sad-kirblet.svg";
---
<div class="{`${Class}" divider pb-5`}">
<image src="{sadKirblet}" alt="Kirblet!" class="w-16" />
</div>
The component demonstrates Astro’s approach: imports happen in the frontmatter, enabling asset optimization through the Image component, while the template renders as standard HTML with class-based styling. Components can be imported directly into MDX files and used like any other Astro component.
Layout System
Astro’s layout system eliminates the need for manual header/footer copying or complex state management for shared components. A single BaseLayout.astro can handle navigation, theming, and responsive structure, allowing every page to focus purely on content. Changes to global elements like dark mode require updates in only one location, automatically applying across all pages.
Layouts compose naturally—blog posts might use a specialized PostLayout.astro which itself extends BaseLayout.astro, creating nested layouts without complexity. This approach maintains clean separation of concerns while enabling sophisticated page structures.
Content Collections
Content Collections provide the benefits of file-based authoring, type-safe schemas enforced at build time, and zero runtime overhead. This combination solves the common trade-off between simple Markdown files and heavy CMS systems.
The content structure uses standard directories with a TypeScript configuration file defining schemas through Zod validation:
src/content/
├── config.ts
├── posts
│ ├── 1-Astro.mdx
└── projects
└── 1-Capsule.mdx
// config.ts
const postsCollection = defineCollection({
type: "content",
schema: ({ image }) => z.object({
title: z.string(),
pubDate: z.string(),
description: z.string(),
author: z.string(),
cover: image(),
}),
});
Content validation happens at build time, providing TypeScript errors for invalid posts before deployment. Dynamic routes use the getCollection function to retrieve content, with the <Content /> component handling rendering automatically.
Styling Approach
TailwindCSS combined with DaisyUI provides a pragmatic styling solution. Tailwind makes styling decisions explicit and discoverable directly in markup, while DaisyUI adds semantic components to reduce repetitive class combinations.
The configuration remains minimal, focusing only on essential themes and custom breakpoints:
// tailwind.config.cjs
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
plugins: [require("@tailwindcss/typography"), require("daisyui")],
daisyui: {
themes: ["luxury", "dim"],
},
theme: {
extend: {
screens: {
xl14: "1440px",
xl2k: "2048px",
},
},
},
};
This approach eliminates the need for custom CSS files while maintaining consistency across the entire site.
Production Considerations
This stack prioritizes simplicity over cutting-edge features. There’s no server-side rendering beyond build time, no edge functions, and no real-time capabilities—just static files deployed to a CDN. These constraints become advantages, preventing over-engineering and focusing energy on content delivery.
The site loads instantly, costs nothing to host, and functions perfectly with JavaScript disabled. Performance optimizations happen automatically through Astro’s build process, while bundle sizes remain minimal without extra configuration.
Astro excels for content-focused sites like portfolios, blogs, documentation, and marketing materials. For interactive applications requiring extensive client-side functionality, other frameworks might be more appropriate. But when the goal is rapid development of performant content sites, Astro provides an ideal balance of simplicity and power.
The final result is a portfolio site that shipped instead of becoming another abandoned project. Sometimes the best framework is the one that stays out of the way and lets you build.