Skip to content

kurtyoon/Devlog

Repository files navigation

Devlog

Kurtyoon's personal developer blog — blog.kurtyoon.me

Posts are plain Markdown files (no CMS, no database), rendered server-side with React Router 7.

Tech stack

  • React 19 + React Router 7 (framework mode, SSR)
  • TypeScript
  • Tailwind CSS 4
  • Vite
  • fp-ts for the data/utility layer
  • pnpm as the package manager

Getting started

pnpm install
pnpm dev          # http://localhost:5173

Scripts

Script Description
pnpm dev Start the dev server with HMR
pnpm build Production build → build/
pnpm start Serve the production build
pnpm typecheck react-router typegen + tsc

Project structure

The app follows Feature-Sliced Design, with imports generally flowing downward (routes → pages → widgets → features → shared):

app/
├── routes/      # React Router file routes (loaders + meta)
├── pages/       # Page-level composition
├── widgets/     # Self-contained UI blocks (navbar, sidebar, markdown, …)
├── features/    # Domain logic + UI (post, archive)
└── shared/      # Config, constants, lib, locale, layout, post content

Path alias: ~/*app/*.

Writing a post

  1. Create app/shared/contents/posts/<number>. <slug>.md. The leading number is parsed into the post id and sort order, so use the next number after the highest existing post.

  2. Add frontmatter:

    ---
    title: My Post Title
    published: 2025-01-01
    description: A short summary
    tags: [Tag1, Tag2]
    category: Category
    thumbnail: https://example.com/cover.png
    draft: false
    ---

    Set draft: true to hide a post from listings.

  3. Run pnpm dev to preview, then pnpm typecheck before committing.

Configuration

Site-wide settings live in app/shared/config/blog.config.ts. A few values come from VITE_* environment variables:

Variable Used for
VITE_DEVLOG_AVATAR_URL Profile avatar
VITE_DEVLOG_BANNERS Comma-separated fallback banners
VITE_UTTERANCE_REPO utterances comments repo

These are read through import.meta.env and are inlined at build time. They must be present when pnpm build runs (or during docker build); injecting them only at runtime has no effect.

Deployment

Docker

docker build -t devlog .
docker run -p 3000:3000 devlog

The Dockerfile runs pnpm build and copies the Markdown posts directory into the runtime image, so the container serves all content without external files. Because VITE_* values are inlined during the build, provide them via a .env file in the project root before building — the build stage copies the project into the image, so import.meta.env picks them up. Values passed only to docker run (or exported in the shell without a .env) have no effect, since the Dockerfile declares no matching ARG/ENV.

The image can be deployed to any Docker-capable platform (Cloud Run, Fly.io, Railway, ECS, …).

Node

The built-in server is production-ready, but it needs more than just build/. At runtime it requires:

  • package.json and the production dependencies (react-router-serve) — install with pnpm install --prod;
  • the app/shared/contents/posts directory — posts are read at runtime relative to the working directory, not bundled into build/.

The simplest setup is to deploy the checked-out repository, then run pnpm install --prod, pnpm build, and pnpm start.

License

MIT © kurtyoon

About

Markdown based blog

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors