Building this site with Astro & TailwindCSS


I wrote this entire site from scratch using Astro, so I thought I’d share my thoughts on the process. I avoided using any templates, because that defeats the purpose of learning. Since CSS makes me cry, I relied on TailwindCSS and DaisyUI to reduce the amount of CSS I had to write. I found using DaisyUI actually super easy and fun to mess around with, surprisingly.

Overall I give the experience a 8/10. I was able to build a pretty nice looking site im satisified with, I was able to avoid any bloat from using a template, and it had minimal frustration. I would definitely recommend Astro to anyone looking to build a static site.

Designing a wesbite with Astro

Astro makes it really easy to divide your site into different parts, and then resuse them all over the place. Astro uses Components, Layouts, and Content Collections. I’ll expand on them below.


Components let us isolate the HTML, Typescript and CSS used to create a part of the site into its own file. We can import that component into other components, into layouts and pages and even in our MDX blog posts, as done on this page. We can define a component pretty easily.

# 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" />

In this case, I’m defining a component called Divider.astro. This component is just a small divider with an image in it.

Since we are using the <Image> component, we have to import the image in the frontmatter before we can use it. This allows astro to optimize the image for us.

I can even use it here in this MDX post by simply importing it like normal

import Divider from "../../components/Divider.astro";
<Divider /




We can define common layouts and use that as the basis for individual pages. I use a BaseLayout.astro that consists of my Head and Navbar components. These components contain the responsive CSS in them, as well as handling the theming and other scripts that would always need to be on every page.

# src/layouts/BaseLayout.astro
import Navbar from "../components/Navbar.astro";
import Head from "../components/Head.astro";
    <div class="mx-auto w-11/12 xl14:w-10/12 xl2k:w-8/12">
      <Navbar />
      <slot />
    <script src="../scripts/logResize.ts"></script>

Now I just use this BaseLayout.astro as the layout for all my pages. For example, my 404.astro page looks like this:

# src/pages/404.astro
  <Divider />
    <div class="grid">
      <article class="prose mx-auto">REST OF CONTENT GOES HERE</article>

Just like components, you can reference layouts and resuse them anywhere. I use a PostLayout.astro for my blog and project posts, which in turn uses the BaseLayout.astro as its layout.

Content Collections

If you create a src/pages/ file, Astro will create a banana route for you. This is great for unique pages like the homepage or 404 pages, but what about content like blog posts? We wouldn’t want to manually create a new file for every blog post we write. This is where content collections come in.

I’ve created two content collections, posts and projects

├── config.ts
├── posts
│   ├── 1-Astro.mdx
└── projects
    └── 1-Capsule.mdx

We define how the content should be structured in config.ts. This is where we define the schema for our content. We use the Zod library to define the schema.

  # 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(),

We can now create content in the posts directory, and Astro will automatically create a route for each post. The schema we defined enforces that each post has a title, pubDate, description, author, and cover image. We can then use this content in our pages.

// 1-astro.mdx
title: "Building this site with Astro & TailwindCSS"
pubDate: "2024-01-18"
description: "I wrote this entire site from scratch using Astro, so I thought I'd share my thoughts on it."
author: "Manveer Bhullar"
cover: "../../images/buildingAstro.png"

The final part is to make the routes for each post. We can do this in a src/pages/[...slug].astro file. This is a special file that will match any route that doesn’t match a file in the src/pages directory. We can then use the slug to find the correct post in our content collection.

import { getCollection } from "astro:content";
import PostLayout from "../../layouts/PostLayout.astro";

export async function getStaticPaths() {
  const postEntries = await getCollection("posts");
  return => ({
    params: { slug: entry.slug },
    props: { entry },

const { entry } = Astro.props;
const { Content } = await entry.render();

<PostLayout back="/posts/">
  <Content />

The <Content/> component is a special component that Astro provides. It will render the content of the post, which we then pass on to the PostLayout.astro component.


I didn’t change too much from the default TailwindCSS configuration. I added some custom screens, and defined just the two themes I wanted to use to keep the file small. The custom screen sizes are for breakpoints in css classes. (Used like <div class="lg:hidden dropdown">)

// tailwind.config.cjs
/** @type {import('tailwindcss').Config} */
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",

I have two plugins installed.

  • DaisyUI - A component library for TailwindCSS. I used the classes from this library to style my site. It has a lot of components, and is very easy to use. I highly recommend it.
  • TailwindCSS/Typography - The recommended plugin for TailwindCSS that adds some nice typography styles. I used this to style my blog posts.


Projects Home Posts