No description
  • Astro 51.4%
  • CSS 34.3%
  • TypeScript 10.9%
  • JavaScript 2.5%
  • Dockerfile 0.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-06 17:07:51 +00:00
.github ci: use shared Docker build action 2026-10-20 13:52:31 -05:00
public fix: remove old quartz public files 2026-05-12 11:35:15 -05:00
src docs: update blog content 2026-11-06 2026-11-06 17:07:51 +00:00
.envrc fixing conflicts 2026-01-26 15:09:31 -06:00
.gitattributes add gitattributes for windows 2023-08-02 20:59:56 -07:00
.gitignore feat: migrate blog to Astro with new design and content pages 2026-05-12 10:41:04 -05:00
.node-version Node 22 (#1997) 2025-05-28 16:20:59 -07:00
.npmrc add engines field 2023-08-20 08:57:56 -07:00
.prettierignore fix notes 2023-08-07 23:57:24 -07:00
.prettierrc Use semi: false for prettier config 2022-05-02 08:57:25 -07:00
astro.config.mjs feat(blog): site cleanup 2026-10-01 16:35:48 +00:00
devbox.json fixing conflicts 2026-01-26 15:09:31 -06:00
devbox.lock fixing conflicts 2026-01-26 15:09:31 -06:00
Dockerfile fix: right dockerfile and nginx 2026-05-12 10:41:09 -05:00
LICENSE.txt add base structure 2021-07-18 09:35:42 -04:00
nginx.conf docs: update blog content 2026-05-12 2026-05-12 12:15:19 -05:00
package-lock.json chore(deps): bump the production-dependencies group across 1 directory with 4 updates 2026-11-02 05:56:33 +00:00
package.json chore(deps): bump the production-dependencies group across 1 directory with 4 updates 2026-11-02 05:56:33 +00:00
README.md feat(blog): site cleanup 2026-10-01 16:35:48 +00:00
Taskfile.yaml docs: update blog content 2026-10-29 2026-10-29 14:52:16 +00:00
tsconfig.json feat: migrate blog to Astro with new design and content pages 2026-05-12 10:41:04 -05:00

blog.mvaldes.dev

English personal blog. Built with Astro 6, shipped as a static site behind nginx in a container.

Stack

  • Astro 6 with the Content Layer API
  • JetBrains Mono typography, custom CSS (no Tailwind, no UI lib)
  • One posts collection, currently publishing English posts
  • Single English RSS feed
  • Shiki syntax highlighting (github-dark-dimmed)
  • Static build → nginx → container image

Quick start

npm install
npm run dev      # local dev server on http://localhost:4321
npm run build    # output to ./dist
npm run preview  # serve the build
npm run sync     # regenerate astro:content types after schema changes

Node 22 (see .node-version). devbox + direnv are wired up: cd into the repo and npm install runs automatically.

There's also a Taskfile.yaml:

task dev      # npm run dev
task build    # npm run build
task clean    # rm -rf dist
task sync     # rsync posts in from the Obsidian vault
task publish  # git add . && commit "docs: update blog content <date>" && push

Project layout

src/
├── content/
│   └── posts/               # all posts (.md / .mdx)
├── content.config.ts        # collection schema (Zod)
├── i18n.ts                  # UI strings + date helpers
├── lib/
│   └── content.ts           # post helpers
├── layouts/
│   ├── Base.astro           # html lang + meta
│   └── Post.astro           # single-post layout
├── components/
│   ├── Header.astro         # nav
│   ├── Footer.astro
│   ├── PostCard.astro
│   └── Sidebar.astro
├── pages/
│   ├── index.astro          # redirects to /en
│   ├── projects.astro
│   ├── video.astro
│   ├── en/
│   │   ├── [...page].astro  # paginated post list, 8 per page
│   │   ├── about.astro
│   │   ├── posts/[...slug].astro
│   │   ├── tags/[tag].astro
│   │   └── rss.xml.ts
└── styles/
    └── global.css

Dockerfile                    # node build → nginx runtime
nginx.conf                    # server config baked into the image

Cluster manifests are not in this repo. The blog runs on k3s, but the Deployment/Service/IngressRoute live in the gitops repo alongside everything else Flux reconciles.

Adding a post

Drop a .md (or .mdx) file in src/content/posts/. The filename becomes the URL slug, and published English posts land under /en/posts/<slug>.

Frontmatter

---
lang: en                  # required — currently only "en" publishes
title: Self Hosted in 2026
description: Consolidating hardware and software for the homelab
pubDate: 2026-08-04
status: published
tags:
  - homelab
---

Required: lang, title, description, pubDate. Optional: tags (defaults to []), status (defaults to published; use draft for drafts), updatedDate, cover.

Schema lives in src/content.config.ts — that's the source of truth.

Drafts

Set status: draft. Published posts use status: published or omit status entirely. Draft posts stay in the repo, don't build, don't appear in the post list, RSS, or the sitemap.

Spanish content can still live in the collection with lang: es, but it is not routed or published right now.

MDX

Rename to .mdx to import components. Useful for callouts or interactive demos.

Writing flow

Posts are drafted in Obsidian (~/Obsidian/wiki/Blog/, or the WSL path on Windows) and rsync'd into src/content/posts/ with task sync. Frontmatter in the vault must already carry lang — sync doesn't add it. Then task publish commits and pushes, which triggers the build.

Deployment

Local container build

docker build -t blog-mvaldes .
docker run -p 8080:80 blog-mvaldes
# http://localhost:8080

nginx listens on port 80 inside the container (the EXPOSE 8080 line in the Dockerfile is stale and doesn't match nginx.conf).

Pushes are built into a tagged image by CI and rolled out to k3s by Flux from a separate gitops repo.

Locale notes

  • UI strings live in src/i18n.ts.
  • Dates use Intl.DateTimeFormat via the formatDate(date, lang) helper.

TODO

  • Open Graph default image at public/og-default.png (currently 404s)
  • Search — Pagefind is easy to wire up here, runs on the static build
  • Obsidian wiki-link handling — task sync copies posts verbatim, so [[links]] render as literal text
  • Fix the EXPOSE / listen port mismatch between Dockerfile and nginx.conf

License

Content: all rights reserved. Code: MIT — do what you want with the scaffold.