Single-binary self-hosted Markdown blog on Rust axum: no database, live search, Typst PDF export, and strong SEO.
axumblogdockermarkdownminijinjarustself-hostedtypstvite
1use axum::{
2 extract::State,
3 http::{HeaderMap, Uri},
4 response::Html,
5 routing::get,
6 Router,
7};
8use minijinja::context;
9use rand::seq::SliceRandom;
10
11use crate::app::AppState;
12use crate::error::AppError;
13use crate::posts::{self, Post};
14use crate::render::{build_request, render_html};
15
16pub fn router() -> Router<AppState> {
17 Router::new().route("/", get(index))
18}
19
20async fn index(
21 State(state): State<AppState>,
22 uri: Uri,
23 headers: HeaderMap,
24) -> Result<Html<String>, AppError> {
25 let request = build_request(&uri, &headers);
26 let published = posts::published(&state.posts);
27 let latest_post = published.first().cloned();
28 let rest: Vec<Post> = match &latest_post {
29 Some(latest) => published
30 .iter()
31 .filter(|p| p.slug != latest.slug)
32 .cloned()
33 .collect(),
34 None => Vec::new(),
35 };
36 let mut rng = rand::thread_rng();
37 let mut shuffled = rest;
38 shuffled.shuffle(&mut rng);
39 let random_blog_posts: Vec<Post> = shuffled.into_iter().take(3).collect();
40
41 let page = context! {
42 title => "Isaac Bythewood's Blog",
43 slug => "home",
44 description => "Writing about webdev, infrastructure, security, and tooling by Isaac Bythewood, a Senior Solutions Architect in Elkin, NC.",
45 };
46
47 render_html(
48 &state,
49 "home.html",
50 context! { page, latest_post, random_blog_posts },
51 &request,
52 )
53}