Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/blog-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Deploy blog.elimelt.com

on:
push:
branches: ["main"]
paths:
- "blog/**"
- ".github/workflows/blog-deploy.yml"
- ".github/workflows/publish-site.yml"
pull_request:
branches: ["main"]
paths:
- "blog/**"
- ".github/workflows/blog-deploy.yml"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: blog-deploy-${{ github.ref }}
cancel-in-progress: false

jobs:
build:
name: Build blog
runs-on: ubuntu-latest
defaults:
run:
working-directory: blog
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: "22"
cache: npm
cache-dependency-path: blog/package-lock.json
- name: Install dependencies
run: npm ci
- name: Check and build
run: npm run build
- name: Upload build
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: blog-site
path: blog/dist/
if-no-files-found: error
retention-days: 7

publish:
if: github.event_name != 'pull_request'
needs: build
uses: ./.github/workflows/publish-site.yml
with:
site: blog
project: elimelt-blog
host: blog.elimelt.com
secrets:
CLOUDFLARE_PAGES_TOKEN: ${{ secrets.CLOUDFLARE_PAGES_TOKEN }}
8 changes: 4 additions & 4 deletions .github/workflows/publish-site.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name: Publish static site

# Called by web-deploy.yml and notes-deploy.yml after they upload a build
# artifact named "<site>-site". Cloudflare Pages serves the result; nothing
# runs on the homelab.
# Called by web-deploy.yml, notes-deploy.yml and blog-deploy.yml after they
# upload a build artifact named "<site>-site". Cloudflare Pages serves the
# result; nothing runs on the homelab.
on:
workflow_call:
inputs:
site:
description: "Build artifact prefix (web or notes)"
description: "Build artifact prefix (web, notes or blog)"
required: true
type: string
project:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ name: Run Tests
on:
pull_request:
branches: ["main"]
# Notes PRs are validated by notes-deploy.yml.
# Notes and blog PRs are validated by their deploy workflows.
paths-ignore:
- "notes/**"
- "blog/**"

schedule:
# Run daily at 8:00 AM UTC
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/web-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
paths:
- "**"
- "!notes/**"
- "!blog/**"
- "!docs/**"
- "!infra/**"
workflow_dispatch:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Personal Site

This repository holds the public site (elimelt.com), the notes site in [`notes/`](notes/README.md) (notes.elimelt.com), and the homelab in [`infra/`](infra/README.md). The two sites build and deploy independently.
This repository holds the public site (elimelt.com), the notes site in [`notes/`](notes/README.md) (notes.elimelt.com), the blog in [`blog/`](blog/README.md) (blog.elimelt.com), and the homelab in [`infra/`](infra/README.md). The three sites build and deploy independently.

The public frontend uses [Astro](https://astro.build) with TypeScript. Astro renders the portfolio and music pages to static HTML and bundles their interactive modules. The homelab homepage remains a separate TypeScript application.

Expand Down
3 changes: 3 additions & 0 deletions blog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
.astro/
56 changes: 56 additions & 0 deletions blog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Blog

blog.elimelt.com: Elijah's own writing. (notes.elimelt.com is reference
material, often generated or expanded with an LLM.) It's an
[Astro](https://astro.build) site with
[MDX](https://docs.astro.build/en/guides/integrations-guide/mdx/), built to
static files and served by Cloudflare Pages. It builds and deploys on its own
(`.github/workflows/blog-deploy.yml`).

```bash
cd blog
npm ci
npm run dev # http://localhost:3001, drafts included
npm run build # type checks, then builds dist/
```

## Writing a post

Add `src/content/posts/<slug>.md` or `.mdx`; the file name becomes
`/posts/<slug>/`. Frontmatter is checked against `src/content.config.ts`:

```yaml
---
title: Post title
description: One sentence for the index, feed and link previews.
date: 2026-10-25
updated: 2026-11-02 # optional
draft: true # optional; shown in dev, left out of the build
---
```

## Interactive elements

Use `.mdx` and import components:

```mdx
import Figure from '../../components/Figure.astro';
import RandomWalk from '../../components/RandomWalk.astro';

<Figure caption="What the reader is looking at." wide>
<RandomWalk walkers={300} />
</Figure>
```

`src/components/RandomWalk.astro` shows the pattern: markup plus a `<script>`
that defines a custom element. Astro bundles the script and loads it only on
pages that use the component, so posts without interactivity ship no
JavaScript. Anything that runs in a browser works the same way (canvas, WebGL,
npm libraries). To use a UI framework, add its integration (for example
`npx astro add react`) and render the component with a `client:*` directive such
as `client:visible`.

`Figure` frames a component and adds a caption; `wide` lets it extend past the
text column.

Styles are in `src/styles/global.css`; layouts are in `src/layouts/`.
16 changes: 16 additions & 0 deletions blog/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
site: 'https://blog.elimelt.com',
output: 'static',
trailingSlash: 'always',
integrations: [mdx(), sitemap()],
markdown: {
shikiConfig: {
themes: { light: 'github-light', dark: 'github-dark' },
},
},
server: { port: 3001 },
});
Loading
Loading