cat ~/writing/building-this-website.md
Building This Website with Elixir and Phoenix
I wanted to build a personal website as a place to publish writing and experiment with new technologies. I enjoy writing in markdown, so the website had to be 100% server-side rendered with each post stored as (almost) plain markdown. Elixir and Phoenix had been on my radar for a while, and this was the excuse I needed.
NimblePublisher is a library that reads markdown files at compile time and turns them into Elixir structs.
You drop a .md file in priv/posts/, run mix compile, and it becomes data your app can query.
defmodule Website.Posts do
alias Website.Posts.Post
use NimblePublisher,
build: Post,
from: Application.app_dir(:website, "priv/posts/**/*.md"),
as: :posts,
html_converter: Website.Posts.MarkdownConverter
@posts Enum.sort_by(@posts, & &1.date, {:desc, Date})
def all_posts, do: @posts
def get_post_by_id!(id) do
Enum.find(all_posts(), &(&1.id == id)) ||
raise Website.Posts.NotFoundError, "post not found: #{id}"
end
end
Each post file encodes its slug and date in the filename. A file at priv/posts/2026/02-28-building-this-website.md becomes a post with id "building-this-website" and date ~D[2026-02-28].
Markdown rendering
MDEx is a Rust-based markdown parser for Elixir. It handles syntax highlighting at compile time by inlining styles directly into the HTML.
MDExMermex does the same for Mermaid diagrams, rendering them to inline SVG at compile time.
MDEx.to_html!(body,
extension: [
strikethrough: true,
table: true,
autolink: true,
tasklist: true,
header_ids: ""
],
syntax_highlight: [formatter: {:html_inline, theme: "nordic"}],
plugins: [
{MDExMermex, output: :inline_svg, inject_css: false, inject_js: false}
]
)
Everything you see on the page, highlighted code blocks and the diagram below, was rendered during compilation.
Deployment
This website runs on Cloudflare Containers, which is obviously overkill but something I wanted to try. The downside is cold starts — the container sleeps after some specified amount of inactivity. The upside is that a 100% server-side rendered website is very cacheable, so it doesn't really matter. A custom plug sets CDN cache headers and most requests are served from the nearest Cloudflare edge node without ever waking the container.