Personal portfolio and engineering blog, built with Nuxt 4, Nuxt UI, and TailwindCSS.
- Portfolio: hero section, about me, and an experience timeline with technology badges
- Blog: Markdown-powered posts with KaTeX math rendering, syntax highlighting, and estimated reading time
- Dark mode: seamless theme toggle with Nuxt UI
- Fully static: prerendered at build time via Nitro's crawl-links strategy
- SEO-ready: per-page meta tags populated from post frontmatter
- Accessible: built with
@nuxt/a11yand semantic HTML throughout - Testing: three-tier test suite: Vitest unit, Vitest component (Nuxt + happy-dom), and Playwright E2E
| Layer | Technology |
|---|---|
| Framework | Nuxt 4 |
| Component library | Nuxt UI v4 |
| Styling | Tailwind CSS v4 |
| Language | TypeScript 5.9 |
| Content / CMS | @nuxt/content v3 (Markdown) |
| Math rendering | KaTeX (remark-math + rehype-katex) |
| Icons | Iconify (heroicons, lucide, mdi, ri, simple-icons) |
| Images | @nuxt/image |
| Unit / component tests | Vitest 4 + @nuxt/test-utils + happy-dom |
| E2E tests | Playwright |
| Linting | ESLint with @nuxt/eslint-config (stylistic) |
| Package manager | pnpm 10.32 |
| Runtime | Node 24.14 (pinned via mise.toml) |
.
βββ app/
β βββ assets/css/main.css # Global styles, fonts, KaTeX overrides
β βββ components/
β β βββ content/ # Prose overrides for Nuxt Content
β β βββ landing/ # Home page sections (Hero, AboutMe, Experiences, LatestPosts)
β β βββ layout/ # Shell components (AppHeader, AppFooter, AppMain)
β β βββ overlays/blog/ # Blog post components (PostContent, PostInfo, PostTagsβ¦)
β βββ pages/
β β βββ index.vue # Landing page (/)
β β βββ blog/
β β βββ index.vue # Blog listing (/blog)
β β βββ [...slug].vue # Individual post (/blog/...)
β βββ app.config.ts # Nuxt UI theme (primary: blue, neutral: slate)
β βββ app.vue # Root component
β βββ error.vue # Custom error page
βββ content/
β βββ blog/
β βββ 2025/ # Posts from 2025
β βββ 2026/ # Posts from 2026
βββ tests/
β βββ e2e/ # Playwright E2E specs
β βββ nuxt/ # Vitest component tests
β βββ unit/ # Vitest unit tests
βββ content.config.ts # Blog collection schema (Zod)
βββ nuxt.config.ts # Main Nuxt configuration
βββ eslint.config.mjs # ESLint rules
βββ vitest.config.ts
βββ playwright.config.ts
βββ mise.toml # Node + pnpm version pins
Use mise install to set up the correct Node and pnpm versions based on
mise.toml.
pnpm installStart the dev server on http://localhost:3000:
pnpm dev# Production build
pnpm build
# Preview the production build locally
pnpm preview| Script | Description |
|---|---|
pnpm dev |
Start the development server |
pnpm build |
Build for production |
pnpm preview |
Preview the production build |
pnpm lint |
Run ESLint |
pnpm lint:fix |
Run ESLint and auto-fix issues |
pnpm typecheck |
Run TypeScript type checking |
pnpm test |
Run all Vitest tests |
pnpm test:watch |
Run Vitest in watch mode |
pnpm test:coverage |
Run Vitest with coverage report |
pnpm test:unit |
Run unit tests only |
pnpm test:nuxt |
Run component tests only |
pnpm test:e2e |
Run Playwright E2E tests |
pnpm test:e2e:ui |
Run Playwright E2E tests with the UI runner |
Posts live in content/blog/<year>/ as Markdown files. The filename prefix
(e.g. 1.) controls ordering within a year.
Each file must include the following frontmatter:
---
date: 2025-03-10
minRead: 8
image: /path/to/cover-image.png
author:
name: kyomi
description: software engineer
avatar:
src: /github/bitterteriyaki.png
alt: kyomi's avatar
tags:
- algorithms
- math
---KaTeX math is supported inline ($...$) and in display blocks ($$...$$). Code
blocks support syntax highlighting for most languages
This project enforces strict code style via ESLint:
- Imports must be alphabetically sorted
- Vue SFCs must follow the block order:
<script>β<template>β<style> - Max line length in templates: 80 characters
- Up to 5 attributes per line in Vue templates
Run pnpm lint:fix to auto-fix most issues before committing.
This project is licensed under the MIT License. See LICENSE for the full text.
- Email: me@kyomi.dev
- GitHub: @bitterteriyaki