A blog-first personal site built with Astro. Minimal, text-forward, writing-centered. Designed for an Obsidian-based writing workflow with static deployment to GitHub Pages.
Live site: https://www.tim-dennis.com
| Command | Action |
|---|---|
npm install |
Install dependencies |
npm run dev |
Start dev server at localhost:4321 |
npm run build |
Build site + generate Pagefind search index |
npm run build:astro |
Build site only (no search index) |
npm run preview |
Preview the production build locally |
/ Writing-first homepage: recent posts, notes, links to work
/blog All published blog posts, chronological
/blog/archive Archive by year
/blog/tags/[tag] Posts by tag
/notes Quick notes, linkposts, crosspost summaries
/programs Durable program pages
/case-studies Research partnership case studies
/impact Quantitative outcomes
/governance Governance work
/scholarship Publications and citable outputs
/talks-grants Selected talks and grant awards
/about Professional biography
/search Pagefind-powered site search
/rss.xml RSS feed (blog + notes)
Notes live in the vault. The site reads them via symlink — write in Obsidian, publish by changing status: draft to status: published.
~/Documents/tasks/ ← Obsidian vault
site/
notes/ ← publishable notes (symlinked)
~/projects/websites/jt14den-astro/
src/content/
notes → ~/Documents/tasks/site/notes (symlink, active)
blog/ (in repo, not symlinked)
Blog posts stay in the repo for now. Notes live in the vault.
- Create a new file in
~/Documents/tasks/site/notes/using the Site Note Template in Obsidian - Write the note — keep it 200–400 words, one clear point
- Set
status: publishedwhen ready - Run
npm run buildor let the dev server pick it up
Filename convention: YYYY-MM-DD-short-slug.md
Synthesis note (something you keep noticing across projects):
---
title: "The thing I keep seeing"
description: "One sentence for RSS and SEO."
date: 2026-06-06
tags: [infrastructure, open-science]
status: published
type: note
---Linkpost (you read something that connects directly to your work):
---
title: "What this piece gets right about X"
date: 2026-06-06
tags: [ai-agents]
status: published
type: linkpost
externalUrl: "https://..."
---Crosspost (summary of something you published elsewhere):
---
title: "Post title"
date: 2026-06-06
status: published
type: crosspost
canonicalUrl: "https://..."
promoteToBlog: false
---Add promoteToBlog: true to any note to also surface it in /blog/ with a "From Notes" badge.
The vault (reference/wiki/, daily notes, project files) is where you carry things.
A site note is where you set something down — crystallized, in your voice.
What makes a good note:
- You've noticed the same pattern across 2+ projects
- You just figured something out that cost you real time
- You read something that reframed how you think about your actual work
- You want to put a thing down so you can stop carrying it
Dictation → note pipeline:
- Speak the observation (phone Voice Memos, iOS dictation, or any transcription tool)
- Drop the raw transcript into
site/notes/as a draft - Clean up frontmatter, tighten one pass
- Publish
The constraint that makes this sustainable: if you're spending more than 20 minutes on it, it wants to be a blog post.
Add promoteToBlog: true to a note's frontmatter. It will appear in:
/notes/(as a note)/blog/(with a "From Notes" badge)- RSS feed (once)
No file duplication needed.
A crosspost is a locally-written summary of something you published elsewhere (DataSquad blog, IMLS project site, etc.). The canonical URL points to the external source.
---
title: "Post title as it appeared externally"
description: "One sentence summary."
date: 2026-03-04
status: published
type: crosspost
canonicalUrl: "https://ucla-datasquad.github.io/posts/example/"
externalUrl: "https://ucla-datasquad.github.io/posts/example/"
tags: [datasquad, research]
---
Write a summary or excerpt here. The page will show a notice:
"This is a summary. Read the full post at [url]."Search engines will credit the canonical URL, not this page.
For linkposts (brief commentary + external link):
---
title: "Interesting thing I found"
date: 2026-03-04
status: published
type: linkpost
externalUrl: "https://example.com/the-thing"
tags: [link]
---
Brief commentary on why this is interesting.Every blog post and note automatically includes a LinkedIn share link at the bottom of the page. It uses:
https://www.linkedin.com/sharing/share-offsite/?url=<encoded-post-url>
To share manually:
- Open the post on your site.
- Click "Share on LinkedIn" at the bottom of the post.
- LinkedIn will pull the title, description, and OG image from the page metadata.
Make sure your post has description set for the best preview card.
The site uses Pagefind for lightweight, fully static search.
The search index is generated automatically when you run:
npm run buildThis runs astro build followed by npx pagefind --site dist, creating dist/pagefind/ with the index and UI.
During local dev (npm run dev), the search page will show a notice that the index isn't available. Run npm run build && npm run preview to test search locally.
The site deploys via GitHub Actions on every push to master.
- In your GitHub repo, go to Settings → Pages → Source and select GitHub Actions.
- Push to
master— the workflow builds the site and deploys automatically.
- Checks out the repo.
- Installs Node 20 + npm dependencies.
- Runs
astro build. - Runs
npx pagefind --site distto generate the search index. - Uploads and deploys to GitHub Pages.
The site uses www.tim-dennis.com. DNS is configured at Squarespace:
- 4 A records pointing to GitHub Pages IPs
- CNAME
www→jt14den.github.io
public/CNAME contains www.tim-dennis.com.
| Collection | Directory | Purpose |
|---|---|---|
blog |
src/content/blog/ |
Primary writing, essays, longform |
notes |
src/content/notes/ |
Quick notes, linkposts, crossposts |
programs |
src/content/programs/ |
Durable program/initiative pages |
caseStudies |
src/content/case-studies/ |
Research partnership case studies |
scholarship |
src/content/scholarship/ |
Publications, reports, datasets |
talks |
src/content/talks/ |
Talks and grant awards |
| Field | Type | Required | Notes |
|---|---|---|---|
title |
string | yes | |
date |
Date | yes | YYYY-MM-DD |
status |
string | yes | draft or published |
type |
string | yes | essay, note, linkpost, crosspost, roundup, announcement |
description |
string | no | Used in listings, RSS, OG tags |
tags |
string[] | no | |
updated |
Date | no | Last updated date |
canonicalUrl |
string | no | Overrides canonical for crossposts |
externalUrl |
string | no | Link target for linkposts/crossposts |
promoteToBlog |
boolean | no | Notes only: also appear in blog index |