July 16, 2026 · 2 min read

Giving Projects the same treatment as the devlog

Share

The projects page started as a single hardcoded object in the page component — fine for one project, awkward the moment there's a second one worth writing more than two sentences about.

The shape

Same pattern as the devlog: frontmatter'd markdown in src/content/projects/, read off disk at request time.

export interface ProjectMeta {
	slug: string;
	name: string;
	description: string;
	href?: string;
	live?: string;
	cover?: string;
	images: string[];
	tags: string[];
	stack: string[];
	status: ProjectStatus;
	featured: boolean;
	readingTime: number;
	searchText: string;
	draft: boolean;
	date: string;
}

getAllProjects() lists them for the grid, getProject(slug) renders one in full at /projects/[slug] — a straight copy of how devlog.ts already works, down to reusing gray-matter for frontmatter and marked for the body.

What this unlocks

Each project now gets its own page instead of just a card that links out to GitHub — room for a real writeup, screenshots, a live-site link, source link, and tags, without touching a single line of Svelte. Add a .md file, redeploy, done. Exactly the same deal the devlog already had.

This post is itself the first entry that isn't a devlog post about something built — it's the writeup for a feature that's also visible one click away, on the projects page itself.

Update: the copy became a share

That "straight copy" didn't stay a copy for long. devlog.ts and projects.ts ended up with byte-identical readEntryFile() and toDateString() functions — same gray-matter call, same slug-from-filename logic, same fix for gray-matter parsing unquoted frontmatter dates into Date objects instead of strings. Two copies of the same fix is one more place for it to drift, so both now import readEntryFile/toDateString from a shared src/lib/server/content.ts instead of defining their own.

Their toMeta()/getAll()/get() functions stayed separate, though — devlog posts have grown series, a table of contents, search text, and footnotes that projects don't need, so forcing both into one shape would've cost more than the duplication it removed. Share the part that's actually identical, not the part that just looks similar today.