Web

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.

Mokshit Jain · · 8 min read

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

MetricTraditional SPAAstro
Initial JS Bundle~200-500 KB~0 KB
Time to Interactive2-4 seconds< 1 second
Lighthouse Score60-8095-100
SEORequires SSRBuilt-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 load
  • client:idle - Hydrate when browser is idle
  • client:visible - Hydrate when element enters viewport
  • client: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!

Share
Written by
Mokshit Jain

AI engineer & full-stack developer building LLM products, automation, and RAG pipelines.

Continue reading