npm run lint # ESLint + astro check (runs prelint automatically)
npm run build # Static production build
npm run dev # Dev server with hot reload
npm run preview # Preview the buildThe prestart and prebuild scripts automatically encrypt assets before starting the dev server or building.
- Astro 5 — Static site generator with Island Architecture
- Vue 3 — Client-side interactive components (
.client.vuesuffix) - Alpine.js — Lightweight interactions (language selector, date formatting)
- Tailwind CSS 4 — Utility-first styling with typography plugin
- TypeScript — Strict mode with
strictNullChecks
src/
├── components/ # Astro and Vue components
│ └── i18n/ # Translation components
├── content/blog/ # MDX blog posts (en/ and fr/)
├── i18n/ # i18n definitions and utilities
├── layouts/ # Astro layouts (BaseLayout, Layout)
├── pages/ # Astro routes
│ ├── [lang]/ # Localized pages
│ └── og/ # OG image generation
├── utils/ # TypeScript utilities
└── resources/ # Images and assets
integrations/ # Custom remark plugins
projects/ # Local clones of project repos (see below)
scripts/ # Build-time scripts (encryption)
The projects/ directory contains local git clones of the repositories for packages used by this blog (e.g. @tex0l/encrypted-card, @tex0l/ctrk-astro). These are not git submodules — they must be cloned manually:
git clone https://github.com/tex0l/encrypted-card.git projects/encrypted-card
git clone https://github.com/tex0l/ctrk-astro.git projects/ctrk-astroThis allows developing and releasing these packages alongside the blog. The blog itself depends on them via npm (not file: links).
.client.vue: client-only Vue components{number}.{slug}.mdx: blog posts (number defines order)- Collection IDs include the language prefix (
en/orfr/)
- English (
en) — Default language - French (
fr)
import { useTranslations, useTranslatedPath } from '~/i18n/utils'
const t = useTranslations(lang)
const translatePath = useTranslatedPath(lang)
// Access translations
t('about-me.title')
t('index.greeting', ['John']) // Positional interpolation {0}
// Generate localized paths
translatePath('/blog') // → /en/blog or /fr/blogTranslations are in src/i18n/index.ts with hierarchical keys:
layout.*: Navigation, footerindex.*: Home pageabout-me.*: About pageblog.*: Blog list and metadatarss.*: RSS feed
Posts are in src/content/blog/{lang}/ with the schema:
{
title: string, // Required
date: Date, // Default: now
description?: string, // Optional
image?: ImageMetadata, // Optional
alt: string, // Default: "cover picture"
tags: string[] // Default: []
}Syntax in MDX files:
:::note[Optional Title]
Note content
:::
:::tip
Tip without title
:::
:::caution
Warning
:::
:::danger
Danger
:::End-to-end encryption for the business card, provided by @tex0l/encrypted-card:
- Algorithm: AES-256-GCM
- Key derivation: Scrypt (N=1024, r=8, p=1)
- Distribution: Password in URL hash (not sent to server)
scripts/encrypt.js encrypts files from public_to_encrypt/ to public/encrypted/.
The prebuild and prestart scripts automatically run both encryptions (prod and dummy) via .env.local. If PASSWORD is not defined, only dummy encryption runs.
Vue components from @tex0l/encrypted-card handle client-side decryption using the password extracted from the URL hash.
Open Graph images are dynamically generated via astro-og-canvas in src/pages/og/[...route].ts.
Image route: /og/{route}.png
Route naming:
- Blog:
blog-{lang}-{slug}(e.g.blog-fr-hello-world) - Pages:
{lang}-{page}(e.g.en-about-me)
- Always use
validateLang()to validate language parameters - Prefer type guards and assertions for type safety
- Use the
~/alias for imports fromsrc/
- Prefer Astro components (
.astro) for static content - Use Vue (
.client.vue) only for client-side interactivity - Use
client:only="vue"for Vue components without SSR
- Use Tailwind utility classes directly
@tailwindcss/typographyplugin for prose styling in articles- Avoid CSS modules, prefer utility classes
- Static build only, no SSR
- Image optimization via Astro's
Picturecomponent - View Transitions via Astro's
ClientRouter
| Package | Usage |
|---|---|
@tex0l/encrypted-card |
Encrypted business card components |
@tex0l/ctrk-astro |
CTRK telemetry exporter components |
astro-og-canvas |
OG image generation |
astro-icon |
Icon system |
@iconify-json/* |
Icon collections (carbon, mdi, octicon, emojione, noto) |
luxon |
Date formatting with i18n support |
unified + remark-* |
MDX processing pipeline |
-
OG images not generated for a language: Check that the article ID includes the correct language prefix in
src/pages/og/[...route].ts -
Missing translations: Check the console for missing i18n key warnings
-
Type errors after content changes: Run
npm run lint(includesastro sync) -
Encryption fails: Check that
PASSWORDis defined in.env.local(dummy encryption always works)