Personal website built with SvelteKit 2, Svelte 5, and TypeScript. Features a blog with real-time WebSocket comments, photography gallery, and project showcase. Deployed on Vercel with a separate Go backend API.
- Framework: SvelteKit 2 with Svelte 5 runes (
$state,$derived,$effect) - Styling: Tailwind CSS 4
- Markdown:
marked+sanitize-htmlwith custom[[embed|url]]syntax - Package Manager: Bun
- Deployment: Vercel (adapter-vercel with ISR)
- Backend: Go service at
PUBLIC_BACKEND_URL
- Blog: Markdown-based posts with syntax highlighting, Mermaid diagrams, and link embeds
- Real-time Comments: WebSocket-powered live comment updates with exponential backoff reconnection
- CMS Pages: Home and project pages backed by backend API
- Link Embeds: Notion-style
[[embed|url]]syntax with SSR preview fetching - ISR: Incremental Static Regeneration (10min for pages, 1h for blog posts)
- Bun 1.0+
- Node.js 20+
bun installbun run dev # Start dev server (localhost:5173)
bun run check # Type check with svelte-check
bun run lint # ESLint
bun run format # Prettier format
bun run test # Run all tests once
bun run build # Build for production (Vercel adapter)Copy .env.example to .env and configure:
PUBLIC_BACKEND_URL=http://localhost:8080
PUBLIC_BACKEND_WS_URL=ws://localhost:8080
PUBLIC_APP_ENV=development
BYPASS_TOKEN=your-bypass-token
AUTH_TOKEN=your-auth-tokensrc/
lib/
api/ # Backend HTTP wrappers (always use fetchWithTimeout)
components/ # Reusable Svelte components
config.ts # API endpoints + fetchWithTimeout
stores/ # Svelte 5 rune stores (auth, websocket, toast)
types/ # TypeScript interfaces
util/ # Utilities (markdown, embed, debounce)
routes/
+page.{svelte,server.ts} # Home page (CMS-backed, ISR 10min)
project/+page.{svelte,server.ts} # Project page (CMS-backed, ISR 10min)
blog/[slug]/+page.{svelte,server.ts} # Blog post (ISR 1h)
blog/+page.{svelte,server.ts} # Blog list
- State Management: Svelte 5 runes (
$state,$derived,$effect). Files exporting reactive state end in.svelte.ts - API Layer: All backend calls through
lib/api/withfetchWithTimeout()(5s default) - Markdown Rendering: Always use
renderMarkdown(text, previews?)fromlib/util/markdown.ts - Server Modules: Files ending in
.server.tsare server-only (e.g.,embed-previews.server.ts) - WebSocket: Real-time comments via
websocket.svelte.tswith auth-required connection
Inline chip rendering for URLs, similar to Notion's "paste as mention":
- Syntax:
[[embed|https://example.com/path]] - Rendering:
<a class="link-embed-chip">[favicon] <title or domain></a> - Editor: Slash menu or paste handler (auto-converts URLs on empty lines)
- Preview: SSR via
preloadEmbedPreviews()— no client-side flash on public pages
Tests use Vitest with @testing-library/svelte. Test files use .test.ts extension alongside source files.
bun run test # Run once
bun run test:unit # Watch modeDeployed on Vercel with the @sveltejs/adapter-vercel. Build command runs vite build followed by package publishing steps.
- Backend API — Go service providing blog, page, and WebSocket endpoints