Production-ready Astro blog using Tailwind CSS v4 and Starwind UI patterns, with a full theming system via CSS variables and reusable .astro components.
Demo at https://free-astro-template.netlify.app/
- ✅ Astro SSG with fast dev experience
- ✅ Tailwind CSS v4 + Starwind UI patterns and theming
- ✅ Dark theme via CSS variables (
.dark/[data-theme="dark"]) - ✅ Centralized data in TypeScript (
skills,work,studies,projects,logos) - ✅ Reusable UI components (Badge, Button, Timeline, Carousel, Separators)
- ✅ Organized sections (Header, Hero, Skills, Work Experience, Studies, Footer)
- ✅ Blog with Markdown & MDX content collections
- ✅ SEO: canonical URLs, OpenGraph, sitemap, RSS feed, sharing icons, etc
- ✅ Accessibility-oriented tokens (contrast-ready) and card surface utilities
- Node.js 18+ (or 20+ recommended)
- npm (or pnpm/yarn)
# 1) Install deps
npm install
# 2) Start dev server
npm run dev
# open http://localhost:4321Build and preview:
npm run build
npm run previewfree-astro-template/
├── public/
│ └── favicon.svg
├── src/
│ ├── assets/
│ │ ├── icons/ # Inline icons for UI (e.g., timeline)
│ │ ├── images/ # JPG/PNG hero/blog images
│ │ └── logos/ # Brand logos used by Carousel & projects
│ ├── components/
│ │ ├── sections/ # Page sections
│ │ │ ├── Header.astro
│ │ │ ├── Footer.astro
│ │ │ ├── HeroSection.astro
│ │ │ ├── SkillsSection.astro
│ │ │ ├── WorkExperience.astro
│ │ │ └── Studies.astro
│ │ └── ui/ # Reusable UI components
│ │ ├── badge/Badge.astro
│ │ ├── button/Button.astro
│ │ ├── card/ # Card variants (e.g., PostCard)
│ │ ├── carousel/Carousel.astro
│ │ ├── separator/
│ │ │ ├── ArrowSeparator.astro
│ │ │ └── GlowLineSeparator.astro
│ │ └── timeline/Timeline.astro
│ ├── content/
│ │ └── blog/ # Markdown & MDX posts
│ ├── data/ # All structured data for sections/UI
│ │ ├── skills.ts
│ │ ├── work.ts
│ │ ├── studies.ts
│ │ ├── projects.ts
│ │ └── logos.ts # Globs SVG logos and normalizes names
│ ├── layouts/
│ │ └── BlogPost.astro
│ ├── lib/
│ │ └── svg.ts # sanitizeToOutline(svg, size)
│ ├── pages/
│ │ ├── index.astro
│ │ ├── about.astro
│ │ ├── portfolio.astro
│ │ ├── demo.astro # Component showcase
│ │ └── blog/[...slug].astro
│ └── styles/
│ ├── global.css # Imports base styles in correct order
│ ├── starwind.css # Tailwind + Starwind base & layer order
│ ├── tokens.css # Light tokens & Starwind variable mapping
│ ├── themes/dark.css # Dark theme overrides
│ └── utilities.css # Project utilities, effects, animations
├── astro.config.mjs
├── starwind.config.json
├── tsconfig.json
└── package.json
- Header: favicon brand mark, left‑aligned nav, social icons with hover color.
- HeroSection: primary CTAs via shared
Buttoncomponent. - SkillsSection: pulls data from
src/data/skills.ts; icon hover effects. - WorkExperience: uses
Timeline.astro, icons sanitized vialib/svg.ts. - Studies: simple list of studies from
src/data/studies.ts. - UI components:
Badge,Button,Timeline,Carousel,ArrowSeparator,GlowLineSeparator.
Note: Keep components free of scoped CSS whenever possible. Prefer utilities in styles/utilities.css and design tokens in styles/tokens.css and styles/themes/dark.css.
This template uses Starwind UI’s theming patterns with Tailwind v4:
src/styles/starwind.css: imports Tailwind and Starwind base layers.src/styles/tokens.css: base light tokens and maps to Starwind variables.src/styles/themes/dark.css: dark mode overrides (backgrounds, text, shadows, semantic colors).src/styles/utilities.css: project‑specific utilities and effects, including:.card-surfacesurface style (translucent background, radius, shadow, backdrop-filter)- Carousel animation (
@keyframes carousel-scroll,.animate-carousel) - Separator effects (arrow and glow line)
Dark mode is enabled by applying .dark or [data-theme="dark"] on a root element. The home page defaults to dark. To customize:
- Edit tokens in
styles/tokens.css(light theme base). - Refine dark theme overrides in
styles/themes/dark.css(e.g.,--background,--card, shadows, semantic colors). - Prefer adjusting
.card-surfaceand utilities instyles/utilities.cssinstead of component‑scoped styles.
Accessibility: If Lighthouse flags contrast, slightly increase background luminance and/or raise text contrast by tweaking tokens in dark.css (e.g., --background, --card, and text color variables).
- Blog posts: add Markdown or MDX files under
src/content/blog/. - Skills: edit
src/data/skills.ts(icon, title, description). Inline SVGs are supported. - Work experience: edit
src/data/work.ts(title, company, region, description, technologies). - Studies: edit
src/data/studies.ts. - Projects: edit
src/data/projects.ts(icon, title, stack, description, link). - Logos for carousel: drop SVGs into
src/assets/logos/. They’re collected and normalized bysrc/data/logos.tsusingimport.meta.glob(configured for Vite v5 with{ query: '?url', import: 'default', eager: true }). Names like "Something-logo.svg" are normalized by removing the "logo" suffix and title-casing automatically.
- Use
lib/svg.ts→sanitizeToOutline(svg: string, size: number)to normalize inline SVGs (removes XML/comments, forcesfill="none",stroke="currentColor", and applies width/height). This keeps timeline icons crisp and themable.
- Component:
src/components/ui/carousel/Carousel.astro - Data:
src/data/logos.ts - Props:
tools: array of normalized logos fromlogos.tsspeedMs(optional): lower value → faster scroll. Increase to slow down.
- Behavior: logos render as white by default; on hover, they reveal their original colors.
- Drop SVGs in
src/assets/logos/and they’ll be auto‑globbed bysrc/data/logos.ts. Names are normalized (removes-logo, title‑cases, etc.).
- Use the
.card-surfaceutility for consistent cards:- dark, translucent background via
--card - no border; rely on shadows for depth
- heavier base shadow in dark mode; lighter on hover with accent glow
- dark, translucent background via
- Tweak in
styles/utilities.cssandstyles/themes/dark.css.
- Header nav is left‑aligned; social icons use group hover to change icon color on hover.
- Footer includes a CTA with a primary button.
| Command | Action |
|---|---|
npm install |
Install dependencies |
npm run dev |
Start dev server at http://localhost:4321 |
npm run build |
Build production site to ./dist/ |
npm run preview |
Preview your build locally |
npm run astro ... |
Run CLI commands like astro add, astro check |
npm run astro -- --help |
Get help using the Astro CLI |
- Keep
.astrofiles free of component‑scoped CSS unless absolutely necessary. - Centralize design decisions in
tokens.css,themes/dark.css, andutilities.css. - Prefer data‑driven components: move content into
src/data/and import into sections. - Use inline SVGs for icons when you need themable stroke/fill control; otherwise reference static assets from
src/assets/.
Visit /demo to see the themed Starwind UI components (Badge, Button, Timeline, Carousel) with this template’s styling.
Docs: Astro Docs • Starwind UI
- Blog content collections (Markdown + MDX) with author, tags, and category taxonomy
- Author pages (slugified), Tag pages, Category pages
- Reading time estimation for cards and posts
- Share buttons (X, HN, LinkedIn, Reddit)
- Previous/Next post navigation
- RSS feeds: global, per‑tag, per‑category (+ autodiscovery
<link>on pages) - SEO: canonical URLs, Open Graph + Twitter cards, JSON‑LD (Article, Breadcrumbs, Organization), sitemap
- Robots:
public/robots.txt - Dark‑mode friendly Markdown defaults (tables, lists, code, blockquotes)
Each blog post (src/content/blog/*.md|mdx) supports the following frontmatter keys:
---
title: "Your post title"
description: "Short summary for cards and meta"
pubDate: 2025-06-19
updatedDate: 2024-07-02 # optional
heroImage: ../../assets/images/your-image.jpg # optional
tags: [astro, ui] # optional, many-to-many labels
category: Growth # optional, 1 broad bucket per post recommended
author: Site Author # optional; defaults to site author
---Notes:
- Author pages use a slugified version of the author name (e.g.,
site-author). - Tag pages use slugified tag names (e.g.,
astro). - Category pages use slugified category names (e.g.,
growth).
- Posts:
/blog/<slug>/ - Author:
/blog/author/<author-slug>/ - Tag:
/blog/tag/<tag-slug>/ - Category:
/blog/category/<category-slug>/
- Global:
/rss.xml - By tag:
/rss/tag/<tag-slug>.xml - By category:
/rss/category/<category-slug>.xml
Tip: Feed readers can auto‑discover tag/category feeds via the <link rel="alternate" type="application/rss+xml"> tags we include on those pages. The footer also provides quick links for Feedly/Inoreader and a “Copy RSS link” action.
src/components/BaseHead.astro: common head tags (canonical, OG/Twitter, robots, theme‑color, RSS link). Images and URLs are absolute toAstro.site.src/layouts/BlogPost.astro: Article OG metas, JSON‑LD (Article + BreadcrumbList).- Blog index and pagination pages include
<link rel="prev/next">. - Sitemap via
@astrojs/sitemapis emitted at build;public/robots.txtpoints to it.
- Enhanced defaults for tables and lists live in
src/styles/utilities.cssunder the.prosescope. - Tables: rounded borders, zebra stripes, responsive overflow; Lists: restored bullets/numbering with proper indentation.
- Cards estimate from Markdown using
calculateReadingTimeFromMarkdown()(src/lib/reading.ts). - Post pages estimate from rendered HTML using
calculateReadingTimeFromHtml(). - Default speed is 200 WPM; tweak by changing the optional parameter in these helpers if you prefer another baseline.
- Tag SEO titles/descriptions: edit
src/data/tags.ts(getTagMeta). - Category SEO titles/descriptions: edit
src/data/categories.ts(getCategoryMeta).
- Static output in
dist/. Any static host works (Vercel, Netlify, Cloudflare Pages, GitHub Pages). - Ensure
siteis set inastro.config.mjsfor correct absolute URLs in feeds and metadata.
Deploy to GitHub Pages with one workflow.
- Configure site and base in
astro.config.mjs(critical)
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
// User/Org Pages (https://<user>.github.io):
// site: 'https://<user>.github.io',
// base: '/'
// Project Pages (https://<user>.github.io/<repo>/):
// site: 'https://<user>.github.io/<repo>/',
// base: '/<repo>/'
site: 'https://your-user.github.io',
base: '/',
});- Enable GitHub Pages in your repo
- Settings → Pages → Build and deployment → Source: GitHub Actions
- Add the workflow (ready to copy)
Create .github/workflows/deploy.yml with:
name: Deploy Astro to GitHub Pages
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install deps
run: npm ci
- name: Build
run: npm run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./dist
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4- Commit and push to
main
- First run may take a minute. Check Actions tab for logs and the Pages URL.
Troubleshooting
- CSS/assets 404 on project pages →
baseis missing or wrong. Setbase: '/<repo>/'and rebuild. - Wrong canonical URLs/RSS →
siteincorrect. Use your real Pages URL (or your custom domain if you set one). - Custom domain (CNAME) → Set
site: 'https://example.com'and keepbase: '/'.
- Set your canonical site URL in
astro.config.mjs:
// astro.config.mjs
export default defineConfig({
site: 'https://your-domain.com',
// ...
})- Update global meta in
src/consts.ts:
export const SITE_TITLE = 'Your Site Title';
export const SITE_DESCRIPTION = 'Short description for social/meta.';-
Author profile in
src/data/author.ts(name, avatar, social URLs). -
Replace favicon/app icons in
public/(keep file names the same). -
Optional: Customize theme tokens in
src/styles/tokens.cssand dark overrides insrc/styles/themes/dark.css.
This template ships without analytics by default. To add GA4 later, place the GA snippet in src/components/BaseHead.astro and (optionally) guard it with import.meta.env.PROD.
- Categories: frontmatter supports a single
category. To disable category pages entirely, removesrc/pages/blog/category/[category].astroand the category RSS route; posts will continue to work with tags. - Per-tag/category RSS: remove the routes under
src/pages/rss/tag/andsrc/pages/rss/category/if you only want a global feed.
If you deploy to Netlify, use:
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/404.html"
status = 404-
astro.config.mjssite URL set -
SITE_TITLE/SITE_DESCRIPTIONupdated - Author data and avatar set
- Favicons/app icons replaced in
public/ - Social share image (fallback) looks good
- Lighthouse pass (SEO + Accessibility)
- Deploy (Netlify/Vercel/etc.) and verify
/rss.xmland/sitemap-index.xml
MIT — you’re free to use, modify, and redistribute. See LICENSE for details.
Check my site live at 'https://guihubie.com'