Skip to content

Repository files navigation

bilguun.me

My personal site — a room for my thoughts, creations, and curiosities. Built on Next.js 16 (App Router) with an Obsidian vault as the content source: I write Markdown/MDX in content/, commit, and the site renders it.

Run it

npm install
npm run dev      # development on http://localhost:3000
npm run build    # production build
npm start        # serve the production build

How it works

Content lives in content/ (an Obsidian vault). Each post is a Markdown/MDX file with YAML frontmatter. At build time src/lib/content.ts reads the vault with gray-matter, and pages render the body with next-mdx-remote/rsc.

A post's label decides where it appears and how it looks:

Label Appears on Lives in Shape
article /blog content/blog/*.md Single file
essay /blog content/blog/*.md Single file
internship /blog content/blog/*.md Single file
contest /blog content/blog/*.md Single file
book-review /blog/[slug] content/blog/*.md Single file, Goodreads-style page
book-note /notes content/book-notes/[book]/ Folder: index.md + 1章 … chapters
lesson-note /notes content/lesson-notes/[course]/ Folder: index.md + lesson files
project-log /creations/[slug] content/project-notes/[project]/ Folder: index.md + log-entry files, attached to a creation via project-nickname

Creations (apps/experiments) live in content/creations/ and render at /creations. A creation can optionally have an attached project-log (see above) — a chaptered build log that renders as a "Project Log" section on the creation's page, with each entry getting its own page at /creations/[slug]/log/[entry].

/blog renders in the order: article → essay → internship → contest. Notes, lessons, and project logs are directory-based: the index.md is the cover/overview and each sibling .md is a chapter, with auto prev/next navigation and a chapter sidebar. Chapter files can declare new-word vocabulary, surfaced as a flip-to-reveal review widget.

Authoring: copy a file from content/templates/ and fill it in. The full guide (in Mongolian) is content/vault-guide.md; the field reference is content/templates/README.md.

Translations

A post can exist in several languages without changing its URL. Write one file per language, giving each the same translation-key plus its own lang (en / mn / ja):

# hello-world.md            # hello-world-en.md
lang: mn                    lang: en
translation-key: hello      translation-key: hello

How the logic resolves which version to show:

  • Listings collapse each translation group to one card — the variant matching the reader's language, falling back to the original (a file with no lang), then the site default (en), then the first. This applies to /blog, /notes, related posts, the home featured row + latest feed, and the tag graph — so the same content never shows two or three times.
  • A "Also in" switcher in the post's right sidebar links the language variants; the current one is highlighted.
  • SEOhreflang alternates between the variants plus a canonical are emitted automatically.
  • Posts without a translation-key are standalone and always shown as-is.

The active locale is read from a locale cookie server-side (src/lib/locale.ts); the root layout seeds LanguageProvider with it so server render and client agree (no flash). Client listings re-collapse live when you switch language; the home page and graph update on the next load. Core logic: src/lib/translations.ts (collapseTranslations, localizePost, pickVariant) with lang/translation-key parsed in src/lib/content.ts (getTranslationSiblings).

Search

Press ⌘K / Ctrl+K (or /, or the Nav magnifier) for a full-text command palette over every post, note, chapter, and creation.

  • Index — built at build time from the same content.ts data and served as a static asset at /search-index.json (force-static, like /og). The browser fetches it once and builds the index in-memory. Zero serverless cost; nothing extra in the function bundles.
  • Multilingual — tokenized with the built-in Intl.Segmenter, so Japanese (no spaces) and Mongolian search correctly, not just English. The same tokenizer runs over documents and queries.
  • Locale-aware — a translated post appears once, in the reader's language (same collapse rule as the listings).
  • Swap seam — all querying goes through searchPosts() in src/lib/search.ts. Client-side MiniSearch is the right call up to ~a few thousand posts; beyond that, reimplement just that function against a server index (e.g. SQLite FTS5 / Turso) — the ⌘K UI doesn't change.

Files: src/lib/search-index.ts (records + Markdown→text), src/lib/search.ts (tokenizer, index, searchPosts), src/components/SearchModal.tsx (palette UI).

Features

  • Content pipelinegray-matter frontmatter + next-mdx-remote RSC; custom MDX components: CodeBlock (Shiki, Tokyo Night), CloudImage, Tweet, Sandpack, Math (KaTeX), YouTube, Video
  • Code blocks — fenced blocks auto-highlight; an info string with a filename (```Character.py) shows the filename as a label and infers the language from the extension
  • i18n — English / Mongolian / Japanese UI, switchable live (src/lib/i18n.ts, LanguageProvider)
  • Content translations — multiple language versions per post via translation-key; listings collapse to the reader's language with fallback, per-post "Also in" switcher, hreflang (see Translations)
  • Full-text search — ⌘K command palette over all posts/notes/chapters/creations; client-side MiniSearch, CJK-aware (see Search)
  • Theme — dark/light, set server-side from a cookie so there's no flash on reload
  • Tag graph — an Obsidian-style force-directed graph of tags ↔ posts ↔ creations at /tags (d3)
  • Reading UX — reading-progress bar, scroll-spy chapters sidebar, mobile ToC, related posts, copy-link-to-heading
  • SEOmetadataBase, per-page OpenGraph/Twitter cards, branded fallback share image (/og via next/og), sitemap.xml, robots.txt, BlogPosting/WebSite JSON-LD, RSS at /feed.xml
  • Vault imagescontent/resources/ is synced into public/resources/ at dev/build (scripts/copy-resources.mjs) so frontmatter covers like resources/images/foo.jpg and Obsidian ![[embed.png]] ship as static assets on any host
  • Background music — an optional music: frontmatter field (a YouTube link or audio file) shows a minimal floating player on the post
  • Project logs — a chaptered build log (content/project-notes/[project]/) attaches to a creation via project-nickname and renders as a "Project Log" section + per-entry pages on /creations/[slug]
  • DatescreatedAt / publishedAt / updatedAt frontmatter (legacy single date still supported); publishedAt drives ordering, and updatedAt shows an "Updated" note next to the date when a post has been revised

Routes

Route Description
/ Hero + animated tag graph, featured creations/posts, latest activity grid
/blog, /blog/[slug] Writing, grouped by label; book-reviews get a Goodreads-style header
/notes, /notes/[slug], /notes/[slug]/[chapter] Book & lesson notes (directory-based, chaptered)
/creations, /creations/[slug] Apps, tools, experiments (cover/YouTube/demo/repo)
/creations/[slug]/log/[chapter] Project-log entry (build-log chapter attached to a creation)
/about Bio, experience timeline
/tags, /tags/[tag] Interactive tag graph / posts by tag
/feed.xml, /sitemap.xml, /robots.txt, /og RSS, sitemap, robots, share image
/api/last-visit Previous visitor's geolocation (the "Latest Visit" pill)
/resources/* Vault images/videos, served statically from public/ (synced from content/resources/)

Structure

content/                 Obsidian vault (the CMS)
  blog/                  → /blog        (article, essay, internship, contest, book-review)
  book-notes/[book]/     → /notes       (index.md + 章 chapters)
  lesson-notes/[course]/ → /notes       (index.md + lesson files)
  creations/             → /creations
  project-notes/[project]/ → attaches to /creations/[slug] via project-nickname (index.md + log entries)
  resources/images/      cover images, served via /resources
  templates/             copy-to-author starters (+ README field reference)
  featured.json          slugs featured on the home page
  vault-guide.md         full authoring guide (Mongolian)

src/
  app/                   App Router routes, sitemap.ts, robots.ts, og/route.tsx
  lib/                   content.ts, translations.ts, locale.ts, seo.ts, site.ts, url.ts,
                         i18n.ts, toc.ts, mdx-options.ts, resources.ts, shiki.ts
  components/            shared UI (Nav, Footer, PostPreview, ActivityFeed, TranslationSwitcher, …)
  components/post/       MDX building blocks (CodeBlock, Math, Chapters, VocabReview, Footnote, …)
scripts/                 copy-resources.mjs (vault → public sync)

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages