Building a Newsletter Website with Astro
When building a website for my newsletter, I realized that routing and isomorphic rendering were not my primary concerns. I had no need for frequent database queries or heavy client-side interactivity, which made me wonder whether an SSR framework like Next.js was overkill. Although Next.js also supports fully static exports, the mental overhead of writing React components still gave me pause.
I first heard about Astro from a colleague and thought it was a great opportunity to give it a try. Astro offers a development approach centered around SFCs (Single-File Components), allowing you to build web pages quickly and effortlessly. Architecturally, it embraces cloud deployment trends with optimizations tailored for services like Vercel, Netlify, and GitHub Pages—allowing you to deploy a fully functional website simply by pushing your code.

There are already plenty of static site generators on the market, so you might wonder: why do we need another one? My personal blog has evolved from Jekyll to Hexo, and eventually to Gatsby—transitioning through multiple frameworks along the way. That journey helped me clarify what I truly value when building content-driven websites.
Jekyll and Hexo share many similarities. While they provide the essential features needed to build a content site, extending Markdown syntax or implementing more interactive MDX is relatively difficult, and you are forced to adapt to their proprietary templating syntaxes.
Using Gatsby, on the other hand, required writing GraphQL queries manually. At first, this flexibility was enjoyable, but without a defined schema, adding new frontmatter attributes to Markdown files quickly became a hassle.
Over time, I often found myself stuck in situations where I just wanted to write a simple page, yet had to spend an exorbitant amount of time writing GraphQL queries and React components.
Another alternative is 11ty, which is very similar to Astro. However, I think the biggest difference lies in the templating syntax. 11ty supports multiple templating engines, but for someone like me who has been writing React for years, JSX feels like the most intuitive approach—and Astro delivers exactly that. Below, I’ll share a few aspects where I feel Astro stands out significantly from other generators.
Single-File Components
In Astro’s design, every file can be treated as a self-contained component.
This means JavaScript (which only executes at build time), HTML, and styles can all live within the same file, while the framework automatically handles class name scoping. If you know how to write JSX or Vue, you already know how to write Astro components—no need to learn another templating syntax.
---
const variable = 'Hello';
---
<section>
<p>
{variableA}
</p>
</section>
<style>
p { font-size: 14px; }
</style>
Being able to write JavaScript, HTML, and CSS in a single file makes for a delightful web development experience, and Astro supports this out of the box without requiring any additional configuration.
Although building components feels similar to Vue or Svelte, it is important to note that Astro is neither an SSR platform by default nor a client-side frontend framework. Consequently, pages are only executed at build time, producing zero extra JavaScript by default (as long as SSR mode is not enabled).
What if you need to run JavaScript in the browser for interactivity? Astro provides two approaches:
- Write it inside a
<script>tag. However, since there is no DOM binding mechanism, you’ll need to write vanilla code likedocument.querySelector() - Import components directly from other frontend frameworks
Astro Islands
Sometimes you encounter a scenario where most of the page is purely static, but one or two specific pages—or a small section of a page—require heavy interactivity, such as a blog’s comment section.
For browser interactivity, Astro allows you to import component files from other frameworks like React, Vue, and more—as long as a corresponding adapter exists, you can plug it right in. This feature felt quite unique to me, offering static site generators an extra layer of flexibility.
<aside>
<ReactOrVueComponent client:load />
</aside>
In this code snippet, whenever Astro encounters a non-Astro component, it renders an empty DOM node during compilation, loads the JavaScript on the client side, and mounts the component’s DOM tree there. Here, client:load specifies when the script should be fetched and executed. A thoughtful detail I appreciate is that Astro also provides directives like client:media and client:visible.
Sometimes you only want to load a component on specific devices—such as a swipeable navigation drawer on mobile screens—or only when the user scrolls to a specific viewport location. Without a framework’s help, you would have to write custom JavaScript to handle these cases, but Astro takes care of them out of the box.
This raises a question: how can different frameworks (e.g., Astro and React) share state between each other? Astro already provides a solution for this, though introducing yet another library inevitably makes one wonder whether the benefits outweigh the added complexity.
Personally, I hold a positive view toward adding support for other frontend frameworks inside a static site generator. Astro itself might already satisfy the requirements for most websites; if you truly need to build a heavily interactive app, you wouldn’t choose Astro in the first place. Supporting frontend frameworks simply caters to those edge-case needs: you can scaffold your website rapidly, and when interactive features are truly needed, dropping in Preact makes implementation a breeze.
Content Collections
Astro includes a feature called Content Collections. Simply put, it helps you query static Markdown files. Any file located under src/content/ is treated as part of a content collection.
import { getEntry } from "astro:content";
const entry = await getEntry("weekly", "my-first-weekly.md");
const { Content } = await entry.render();
While Astro can directly render Markdown files as individual pages, it also provides built-in query functions if you want more flexibility over your layout. Calling entry.render() returns a ready-to-use <Content /> component that you can place anywhere in your markup to render the Markdown contents as HTML.
Type Safety
Astro provides first-class TypeScript support. Even for static pages, you can declare props to define which attributes a component can accept, making development much more ergonomic.
// MyComponent.astro
export interface Prop {
user: string
}
// <MyComponent user="kalan" />
Beyond component props, your content can also be strictly typed. For instance, when defining blog posts, we usually use frontmatter to specify metadata such as titles, summaries, images, and so on:
---
title: This is a title
summary: This is a summary
image: https://image.com
---
Astro offers schema definition capabilities (powered under the hood by Zod). By defining your schema types in src/content/config.ts, you automatically get type-safe objects whenever you use getCollection or getEntry.
import { z, defineCollection } from "astro:content";
const weeklyCollection = defineCollection({
type: "content",
schema: z.object({
published_at: z.date(),
issue_num: z.number(),
title: z.string(),
description: z.string().optional(),
image: z.string().optional(),
}),
/* ... */
});
export const collections = {
weekly: weeklyCollection,
};
// Calling it inside another Astro component:
import { getCollection, getEntry } from "astro:content";
const entry = await getEntry("weekly", 'my-first-weekly.md'); // entry is typed according to weeklyCollection defined above
Final Thoughts on Astro
Overall, my experience using Astro has been remarkably smooth. If you don’t need any complex features, writing in Astro feels just like writing plain HTML with support for dynamic variables.
Most static site generators offer roughly similar feature sets, and if you really wanted to achieve the aforementioned capabilities in other tools, you could probably find workarounds. So, what makes Astro truly stand out? In my opinion, it’s the pleasant developer experience, the clean yet flexible syntax (JSX-like), the well-thought-out design for specific edge cases (multi-framework support and versatile script loading strategies), and seamless cloud service integrations.
The way frontend developers approach static site building has changed dramatically in recent years. Ever since Gatsby emerged, developers have been searching for more efficient ways to construct web pages. It’s no longer just about server-side rendering components; managing JavaScript loading strategies, prefetching, Service Worker caching, image optimization pipelines, styling solutions, edge computing, and maintaining rapid development velocity are all responsibilities that modern static site generators must shoulder. It is no longer just about turning Markdown into HTML and uploading it to a server.
Related Posts
- Recreating My Room with Three.js Using React Three Fiber, I brought my real room into the browser—turning physical objects into an interactive table of contents, and using spatial memory to tell the story of my life and work over the past few years.
- Things to Keep in Mind When Using Images in Frontend Development Expanding on Jake Archibald's article, this post organizes how modern responsive images should be written: why width/height are still necessary, when to use CSS aspect-ratio, how to choose between AVIF and WebP, and using picture/source/srcset for art direction on mobile devices.
- CSS field-sizing — Auto-resize Form Elements with a Single Line of CSS Previously, auto-resizing a textarea required listening to scrollHeight in JavaScript. With CSS field-sizing: content, a single line replaces it all, supporting textarea, input, and select. This article covers the pain points of older approaches and how to use field-sizing.
- Make Your Link Underlines Look Better: text-underline-offset By default, underlines sit very close to the text. Some designers dislike this look, and personally, I don't think it looks great either.