Personal website built with Astro and deployed as static HTML.
npm ci
npm run devThe site uses Astro templates, Markdown content, Sass, and vanilla browser JavaScript. It does not use a client-side UI framework or server rendering.
Useful commands:
npm run build # Build CSS and the production site, then verify its contract
npm run check # Type-check Astro and TypeScript
npm run css:watch # Rebuild the fixed-path stylesheet while editing Sass
npm test # Exercise authoring changes in a disposable copy of the siteThe /projects/ page lists personal side projects as a grid, rendered from
src/lib/projects.ts. Keep entries newest first and use existing project
dates rather than inventing exact launch dates. Project artwork lives under
public/assets/work/.
The /investments/ page lists the startup portfolio from
src/lib/investments.ts, oldest investment first. Add only verified company
websites, a short description, and the investment year for each entry; omit
url to show a company without a link. The listing shares the blog's layout
and list styles.
Add posts as dated .md files under _posts/ with title,
date (a quoted YYYY-MM-DD HH:mm:ss timestamp), and ordered categories.
Generate their social cards with npm run og:generate before building.
The build accepts additional posts while protecting the existing URLs listed
in scripts/fixtures/published-routes.json; adding a post does not require
changing a count. The feed includes the newest ten posts.
Article bodies and the Atom feed use the same Astro-rendered content.
Summaries use the same Markdown processor and settings, including smart
punctuation for prose without altering code. Use public /assets/ paths for
images in summaries.
For a new standalone page, add its public URL to SITEMAP_PAGES in
src/lib/sitemap.mjs, or add a documented SITEMAP_EXCLUSIONS entry if it
should not be listed. Post URLs are included automatically and legacy redirects
are excluded. The build independently compares the sitemap with all generated
HTML files, so a forgotten page fails with an actionable message.
The committed package-lock.json, CI, and Netlify continue to use npm. Install
dependencies only with npm ci, not bun install. Bun 1.3.13 or newer can
then run the same scripts with lower startup overhead:
bun run dev
bun run build
bun run checkIn local benchmarks, bun run build was about 34% faster than npm. Bun is an
optional runner here, not a second package manager; Bun lockfiles are ignored
to keep package-lock.json authoritative.
Generate representative title cards:
npm run og:previewPreview images are written to .og-preview/. To test an unusually long title:
node scripts/generate-og-images.mjs \
--title "A deliberately long blog post title" \
--slug custom-previewProduction cards are rendered at 2400Γ1260 and committed with the site. New
posts only need their normal title frontmatter; the image path is derived
from the dated post filename.
After adding or retitling a post, generate and commit its card and manifest:
npm run og:generate
git add public/assets/og/postsThe pull request check rejects stale generated cards. Netlify publishes the committed images directly and does not regenerate them during deploys.
To use custom artwork for a post, add an image override:
image:
path: /assets/images/custom-social-card.png
width: 2400
height: 1260Run npm run og:generate after setting or removing an override to refresh the
manifest and remove any unused generated card. The build validates the override
URL and dimensions instead of requiring a generated image for that post.
Local image paths must exist; external image availability is not checked by the
offline build.
The site-wide fallback card at public/assets/og-image.png is managed manually.
After replacing it, bump its ?v= value in src/lib/site.ts so social crawlers
request the new image.
The /investments/ card at public/assets/og/investments.png uses the post
card renderer. To change it, regenerate, copy it over, and bump the ?v= value
in src/pages/investments/index.astro:
node scripts/generate-og-images.mjs --title "Angel investments" --slug investments
cp .og-preview/investments.png public/assets/og/investments.png