Building Lightning-Fast Sites with Astro and TypeScript
Why Astro is the right tool for content-focused sites, and how it changed the way I think about shipping JavaScript to the browser.
After years of using React, Next.js, and Vue for every project, I discovered Astro—and it completely changed my perspective on when to use JavaScript frameworks. For content-focused websites, portfolios, and documentation sites, Astro delivers unmatched performance and developer experience.
Why Astro?
Astro takes a different approach: islands architecture. Instead of hydrating your entire page with JavaScript, it ships zero JavaScript by default and only adds interactivity where you explicitly need it.
The Performance Difference
| Metric | Traditional SPA | Astro |
|---|---|---|
| Initial JS Bundle | ~200-500 KB | ~0 KB |
| Time to Interactive | 2-4 seconds | < 1 second |
| Lighthouse Score | 60-80 | 95-100 |
| SEO | Requires SSR | Built-in perfect |
Getting Started with Astro
Project Setup
npm create astro@latest my-portfolio
cd my-portfolio
npx astro add tailwind # Add Tailwind CSS
npx astro add react # Add React for islands
Basic Page Structure
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
const pageTitle = 'My Portfolio';
const projects = [
{ title: 'Project A', description: '...' },
{ title: 'Project B', description: '...' },
];
---
<Layout title={pageTitle}>
<main>
<h1>{pageTitle}</h1>
<div class="grid">
{projects.map((project) => <Card project={project} />)}
</div>
</main>
</Layout>
The code between --- is server-side—it doesn’t ship to the browser!
TypeScript Integration
Astro has first-class TypeScript support. Enable it in your config:
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
typescript: {
strict: true,
strictNullChecks: true,
},
});
Type-Safe Components
---
// src/components/ProjectCard.astro
interface Project {
title: string;
description: string;
tags: string[];
image: string;
link: string;
}
interface Props {
project: Project;
}
const { project } = Astro.props;
---
<div class="card">
<img src={project.image} alt={project.title} />
<h3>{project.title}</h3>
<p>{project.description}</p>
<div class="tags">
{project.tags.map((tag) => <span class="tag">{tag}</span>)}
</div>
<a href={project.link}>View Project</a>
</div>
<style>
.card {
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
</style>
Content Collections
For blogs, docs, or any structured content, Astro’s Content Collections are incredible:
// src/content.config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
date: z.date(),
tags: z.array(z.string()),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
Now create markdown files with frontmatter:
---
title: 'My First Post'
description: 'Learning Astro'
date: 2025-02-15
tags: ['astro', 'webdev']
draft: false
---
# Content goes here...
Querying Collections in TypeScript
---
import { getCollection } from 'astro:content';
const allPosts = await getCollection('blog');
const publishedPosts = allPosts
.filter((post) => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
{
publishedPosts.map((post) => (
<article>
<h2>{post.data.title}</h2>
<p>{post.data.description}</p>
<a href={`/blog/${post.slug}`}>Read more</a>
</article>
))
}
Islands Architecture: Adding Interactivity
When you need JavaScript, use React components as “islands”:
---
import InteractiveMap from '../components/InteractiveMap.jsx';
---
<!-- Static content (no JS) -->
<div>
<h1>Contact</h1>
<p>Find us on the map:</p>
<!-- Interactive island (React component) -->
<InteractiveMap client:load />
</div>
Client Directives
client:load- Hydrate immediately on page loadclient:idle- Hydrate when browser is idleclient:visible- Hydrate when element enters viewportclient:media="{query}"- Hydrate when media query matches
Image Optimization
Astro’s built-in image optimization is fantastic:
---
import { Image } from 'astro:assets';
import myHeroImage from '../images/hero.jpg';
---
<Image
src={myHeroImage}
alt="Hero section"
widths={[400, 800, 1200]}
formats={['webp', 'jpeg']}
loading="eager"
/>
This automatically:
- Generates multiple sizes
- Converts to modern formats (WebP)
- Lazy loads by default
- Serves optimal format per browser
Building a Portfolio: Practical Example
Here’s a pattern I use for portfolio projects:
---
// src/pages/projects/[slug].astro
import { getCollection } from 'astro:content';
import ProjectHeader from '../../components/ProjectHeader.astro';
import ProjectGallery from '../../components/ProjectGallery.jsx';
export async function getStaticPaths() {
const projects = await getCollection('projects');
return projects.map((project) => ({
params: { slug: project.slug },
props: { project },
}));
}
const { project } = Astro.props;
const { Content } = await project.render();
---
<Layout title={project.data.title}>
<article>
<ProjectHeader project={project} />
<Content />
<ProjectGallery client:visible images={project.data.images} />
</article>
</Layout>
Performance Tips
1. Minimize JavaScript
# Check your bundle size
npx astro build
npx astro preview
2. Use Preloading
---
<link
rel="preload"
href="/fonts/main.woff2"
as="font"
type="font/woff2"
crossorigin
/>;
---
3. Optimize Images
<Image src={image} formats={['avif', 'webp']} loading="lazy" decoding="async" />
4. Inline Critical CSS
// astro.config.mjs
export default defineConfig({
build: {
inlineStylesheets: 'auto',
},
});
Deployment
Astro builds to static files—deploy anywhere:
# Build for production
npm run build
# Output in /dist - ready to deploy
Deploy to:
- Vercel:
vercel deploy - Netlify:
netlify deploy --prod - GitHub Pages: Use GitHub Actions
- Cloudflare Pages: Direct connect
When to Use Astro
Perfect for:
- Portfolios and resumes
- Blogs and documentation
- Marketing sites
- E-commerce product pages
- Any content-focused site
Not ideal for:
- Complex dashboards
- Real-time applications
- Heavy client-side state
Conclusion
Astro delivers the best of both worlds: the simplicity of static sites with the flexibility of component frameworks. For my portfolio and content sites, it’s been a game-changer—lightning-fast load times, excellent SEO, and a delightful developer experience.
If you’re building a content-focused website, give Astro a try. You might never go back to traditional SPAs for this use case.
Have questions about Astro or want to share your experience? Let’s connect!
AI engineer & full-stack developer building LLM products, automation, and RAG pipelines.
Continue reading

Reducing Story Generation from 20 Minutes to 2 with Temporal.io
How durable workflow orchestration and parallel processing cut AI story generation latency by 10x — a practical guide for Python developers.

Integrating the WhatsApp Business API for Sales Automation
Cutting quotation time from five minutes to under one by wiring the WhatsApp Business API into legacy accounting systems — an automation field guide.

Building RAG Pipelines with pgvector and OpenAI: A Practical Guide
Building production-ready Retrieval-Augmented Generation with PostgreSQL pgvector and OpenAI — and the lessons from hitting 99% retrieval accuracy in production.