Blog
Essay

Technical notes about this site

A technical overview of this personal site, from Nuxt frontend and content modeling to Nitro backend, media pipeline, build, and hosting.

This article was mostly generated by AI and reviewed and edited by me.

nuxtvuenuxt-contentnitroi18n

This article documents the technical structure of this website. It is not just a list of frameworks; it explains how the site brings together a personal homepage, portfolio, knowledge base, blog, gallery, and experimental tools inside one maintainable system.

In one sentence:

This is a Nuxt-based personal knowledge system and portfolio. Content is managed with Markdown and typed collections, pages are rendered with Vue components, a small server surface is provided by Nitro, and production builds output an SSR / Node server bundle with selected routes prerendered.

Frontend

The frontend is built with Nuxt 4 + Vue 3 and uses file-based routing. Core routes include:

  • /: home and profile entry.
  • /about: short profile.
  • /about-this-site: technical overview page.
  • /projects and /projects/[slug]: project index and detail pages.
  • /concepts and /concepts/[slug]: concept notes and local graph views.
  • /blog/posts, /blog/logs, /blog/crap: writing areas with different levels of polish.
  • /gallery: photo and media display.
  • /demo/*: interaction and visual experiments.

The UI layer is built with Nuxt UI, Tailwind CSS 4, custom Vue components, and Markdown content components. Heavier interactive modules such as maps, 3D scenes, Mermaid, Swiper, and Vue Flow are loaded on the client only where they are needed.

The frontend is less like a landing page and more like a long-lived tool surface: the home page is an entry point, article pages are for reading, project pages show deliverables, and concept pages connect ideas.

Content system

The content layer is based on Nuxt Content 3, with schemas defined in content.config.ts.

Main collections:

CollectionSourcePurpose
pagescontent/*.mdTop-level pages
projectscontent/projects/*.mdProjects and portfolio entries
conceptscontent/concepts/*.mdConcept nodes and knowledge graph
postscontent/blog/posts/*.mdFormal articles
logscontent/blog/logs/*.mdTechnical logs and process records
crapcontent/blog/crap/*.mdLighter and less complete notes

Each collection has a schema. For example, projects requires fields such as title, slug, summary, status, updated, and GitHub repo metadata; concepts includes state, relations, relatedProjects, and aliases.

This turns content into structured data rather than a loose pile of Markdown files. Pages query it with queryCollection() and render it with ContentRenderer.

Markdown pipeline

Markdown processing is configured in nuxt.config.ts under content.build.markdown.

It supports:

  • remark-gfm for GitHub Flavored Markdown.
  • remark-math and rehype-katex for math.
  • remark-emoji.
  • Syntax highlighting for zsh, C/C++, Rust, Vue, TypeScript, JavaScript, JSON, Python, ASM, HTML, CSS, YAML, and more.
  • Heading anchors for H1/H2/H3.
  • TOC generation to depth 2.

There is also a custom rubyHook that runs before Content parsing, making ruby / furigana style annotations easier to write in Markdown.

Internationalization

Internationalization is handled by @nuxtjs/i18n. Current locales are:

  • en
  • zh-CN
  • ja

Translation resources live in i18n/locales/, and navigation uses localePath(). Content files also carry lang metadata. For localized content, I group Markdown variants by the same slug, choose the best match for the current locale, and use English as a fallback.

The site does not force every article to exist in all three languages. Some writing is more natural in Chinese, some in Japanese, and some technical material works better in English.

Backend

Server-side capability is provided by Nitro / H3 under server/.

Current server entries include:

  • server/api/github/repo.get.ts: reads GitHub repository, release, and commit data so project pages can show live-ish open-source project status.
  • server/routes/sitemap.xml.ts: generates the sitemap.
  • server/utils/localGalleryUpload.ts: supports a local Gallery upload and import workflow.

The Gallery upload boundary is intentionally strict: it only works in import.meta.dev, only accepts localhost requests, returns 404 or 403 outside local development, and limits accepted file types.

Media pipeline

The Gallery media pipeline is the most backend-like part of this site.

Design goals:

  • iPhone HEIC / Live Photo files can enter a local import workflow.
  • Public display uses generated JPEG / MP4 assets.
  • Original HEIC / MOV files are not uploaded by default.
  • Public assets strip EXIF / QuickTime metadata, especially GPS.
  • Cloudflare R2 is used as object storage.

I keep media upload as a local development tool instead of a production admin button because original photos often contain sensitive metadata. Local processing and explicit upload are easier to control.

Build and runtime

Package management uses pnpm, pinned to pnpm@9.15.9.

Common commands:

pnpm dev
pnpm build
pnpm preview
pnpm generate
pnpm lint
pnpm format:check
[bash] 

Production builds are created with nuxt build. Nuxt generates a client bundle, server bundle, and Nitro output. Current nitro.prerender.routes includes:

  • /
  • /zh-CN
  • /ja
  • /sitemap.xml

So the site is neither purely static nor fully dynamic. It is a hybrid: key entries can be prerendered, while content and APIs are still served by the Nuxt / Nitro runtime.

Hosting and observability

From the repository configuration, the confirmed runtime shape is Nuxt Nitro node-server.

Build output lives in .output/:

  • .output/public: public assets and prerendered output.
  • .output/server: Nitro server bundle.
  • .output/server/index.mjs: Node entrypoint.

Client-side analytics are handled by Vercel Analytics, injected in plugins/analytics.client.ts:

import { inject } from '@vercel/analytics'
[ts] 

Why this architecture

This architecture fits how I use the site:

  • Markdown is good for long-term writing.
  • Typed collections turn content into queryable data.
  • Nuxt keeps content, components, interaction, and server routes in one project.
  • Nitro provides a small backend surface without a separate API service.
  • R2 fits media object storage while keeping uploads and metadata handling locally controlled.
  • Locale metadata lets Chinese, Japanese, and English content coexist.

It is not the simplest blog architecture, but it keeps a personal profile, portfolio, knowledge base, experiment space, and media library inside one maintainable system.