A minimal, read-friendly personal blog built with Astro 5 + MDX.
- Astro 5 β Static site generator
- MDX β Markdown + JSX for rich posts
- React β Interactive islands
- TailwindCSS 4 β Styling
# Clone
git clone https://github.com/serudda/unfiltered.git
cd unfiltered
# Install dependencies
pnpm install
# Run locally
pnpm devOpen http://localhost:4321 to view it.
src/
βββ content/
β βββ posts/ # Your MDX posts go here
βββ components/
β βββ site/ # Header, Footer, PostFooter
βββ layouts/
β βββ Layout.astro # Base layout
β βββ PostLayout.astro # Post-specific layout
βββ pages/
βββ [lang]/ # Route-based i18n (/en/, /es/)
βββ index.astro # Home page
βββ posts/
βββ [slug].astro
- Create a new
.mdxfile insrc/content/posts/ - Add the frontmatter at the top:
---
title: "Your Post Title"
description: "A brief description"
date: 2026-01-15
tags: ["tag1", "tag2"]
draft: false
readingTime: 3
lang: en
ref: your-post-id
---- Write your content below the frontmatter (Markdown + JSX supported)
- Your post will appear at
/{lang}/posts/{filename}
| Field | Required | Description |
|---|---|---|
title |
β | Post title |
description |
β | Short description for SEO |
date |
β | Publication date (YYYY-MM-DD) |
tags |
β | Array of tags |
draft |
β | Set to true to hide in production |
readingTime |
β | Estimated reading time in minutes |
lang |
β | Language code (en or es) |
ref |
β | Shared ID to link translations together |
To create a translation of a post:
- Create another
.mdxfile with a different name - Use the same
refvalue (this links them together) - Use a different
langvalue (enores)
Example:
the-pocket-writer.mdxβlang: en,ref: pocket-writerescritor-de-bolsillo.mdxβlang: es,ref: pocket-writer
The site will automatically generate hreflang tags for SEO.
Place images in public/media/posts/{post-name}/ and reference them:
| Command | Action |
|---|---|
pnpm install |
Installs dependencies |
pnpm dev |
Starts local dev server at localhost:4321 |
pnpm build |
Build production site to ./dist/ |
pnpm preview |
Preview build locally before deploying |
The site is deployed to Netlify. Push to main to trigger a deploy.
This project uses Git Submodules to sync content from external repositories (like skills from other projects).
src/external/
βββ fragments-vault/ # Submodule: github.com/serudda/fragments-vault
βββ .claude/skills/
βββ save-fragment/
βββ SKILL.md # β This file is imported in MDX
# Clone the repo WITH submodules
git clone --recurse-submodules https://github.com/serudda/unfiltered.git
# Or if you already cloned without submodules:
git submodule update --init --recursiveWhen you make changes in the external repo and want them reflected here:
# 1. In the EXTERNAL repo (fragments-vault), make your changes
cd ~/Documents/Projects/SHOWCASE/fragments-vault
# ... edit files ...
git add .
git commit -m "update: improve SKILL.md"
git push
# 2. In THIS project (unfiltered), pull the changes
cd ~/Documents/Projects/unfiltered
npm run sync:external # Updates submodules
# 3. Commit the updated reference
git add src/external/fragments-vault
git commit -m "chore: sync fragments-vault submodule"
git push# Add an external repo
git submodule add https://github.com/USER/REPO.git src/external/REPO
# Example
git submodule add https://github.com/serudda/another-repo.git src/external/another-repoimport { Code } from "astro-expressive-code/components";
import skillCode from "../../external/fragments-vault/.claude/skills/save-fragment/SKILL.md?raw";
## Install This Skill
<Code code={skillCode} lang="markdown" title="SKILL.md" />
β οΈ Important: The?rawsuffix is required to import as plain text.
| Command | Description |
|---|---|
npm run sync:external |
Pull latest changes from all submodules |
git submodule status |
View submodule status |
git submodule update --init |
Initialize submodules after cloning |
git submodule update --remote |
Update to the latest version from remote repo |
Netlify is configured to automatically initialize submodules on each build (see netlify.toml).
Vaults with a submodule field in their frontmatter automatically get a Download button.
During build, the prebuild script generates ZIP files:
npm run generate:zips # Manual generation
npm run build # Auto-runs prebuild β generate:zipsTo enable downloads for a vault:
# In src/content/vaults/my-vault.mdx
---
name: "My Vault"
submodule: "my-submodule-folder" # β folder name in src/external/
---The ZIP includes the entire submodule contents except .git and LICENSE.md.
Submodule is empty after cloning:
git submodule update --init --recursiveChanges not reflecting:
- Did you push in the external repo?
- Did you run
npm run sync:external? - Did you commit the submodule change in this repo?
Download button not appearing:
- Ensure
submodulefield is set in vault frontmatter - Run
npm run generate:zipsto create the ZIP file
Made with β€οΈ by @serudda