Type something to search...
Building Lightning-Fast Sites with Astro and TypeScript

Building Lightning-Fast Sites with Astro and TypeScript

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} />)}
    </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 {
    /* Scoped CSS - no global pollution! */
    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"]}
  Modern
  formats
  first
  loading="lazy"
  Lazy
  load
  below
  fold
  decoding="async"
  Decode
  asynchronously
/>

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!

Related Posts