This is the first article on the rebuilt wzk.icu, documenting the reconstruction process and the reasoning behind my tech stack choices.

Why Rebuild?

The original site was a hand-written HTML + CSS page—visually decent, but painful to maintain:

  • Want to write a blog? Couldn’t do it locally, had to post on CSDN/Juejin, traffic went to other platforms
  • Want to add new content? Edit HTML directly, always worried about breaking styles
  • SEO? Barely any, meta tags were just thrown in randomly
  • Version control? Code was in Git, but content and code were completely mixed

The goal was clear: write articles conveniently in Markdown, have complete SEO infrastructure, consistent styling, and maintainable long-term.

Why Astro?

I considered several options:

OptionProsWhy Not Chosen
Next.jsLarge ecosystem, powerfulToo heavy, personal site doesn’t need SSR, Vercel free tier is limited
HugoExtremely fast, pure staticHigh learning curve for templating, JSX/components are more intuitive
VitePressDocumentation-focusedNot suitable for portal pages, limited customization
AstroSSG, component-based, content collections✅ Perfect fit

Astro’s core advantages:

  • Island Architecture: Zero JS by default, only components needing interactivity load JS
  • Content Collections: Built-in Markdown content management with Schema validation
  • Framework agnostic: Can use React/Vue/Svelte for components, but this site works fine with pure .astro

Pitfalls Encountered

Using Bun Instead of npm

Node 18 was too old, npm create astro threw errors directly. Switched to Bun:

bun create astro@latest

Bun is much faster than npm, package installation feels 3-5x quicker. But be careful—all commands must use bun run, don’t mix with npm.

Astro 6 Content Collections New API

Most tutorials online are still for Astro 4/5, Astro 6 has several breaking changes:

Config file location changed:

Old: src/content/config.ts
New: src/content.config.ts (moved to root)

Loader syntax changed:

// Old syntax
const blog = defineCollection({ type: 'content', schema: z.object({...}) });

// New syntax (Astro 6)
import { glob } from 'astro/loaders';
const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
  schema: z.object({...}),
});

Post rendering changed:

<!-- Old: needs async render() call -->
const { Content } = await post.render();
<Content />

<!-- New: use rendered.html directly -->
<div set:html={post.rendered.html} />

Route params changed:

// Old: post.slug
// New: post.id
params: { slug: post.id }

I hit all these pitfalls during migration, spent quite some time debugging.

Fixed Header Covering Content

Header was set to position: fixed, but the page main didn’t have corresponding padding-top, content got covered.

Fix: Add padding-top: 56px to each page’s main (matching Header height).

SEO Infrastructure

This reconstruction made SEO a first-class requirement, not an afterthought:

  • SeoHead Component: Unified management of <title>, meta description, OG, Twitter Card, canonical
  • JSON-LD: Person + WebSite + Article structured data, lets search engines and AI correctly identify content
  • Sitemap: Auto-generated with @astrojs/sitemap
  • robots.txt: Dynamically generated, pointing to sitemap
  • RSS Feed: Combined blog + projects subscription
  • llms.txt: Site description specifically for AI search engines (like robots.txt, but for LLMs)

Result

After building, it’s pure static HTML, hosted on Vercel, loads extremely fast, low maintenance cost. Writing articles only requires creating a new Markdown file in src/content/blog/, push to GitHub, Vercel auto-builds and deploys.

This is what a personal site should be.