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:
| Option | Pros | Why Not Chosen |
|---|---|---|
| Next.js | Large ecosystem, powerful | Too heavy, personal site doesn’t need SSR, Vercel free tier is limited |
| Hugo | Extremely fast, pure static | High learning curve for templating, JSX/components are more intuitive |
| VitePress | Documentation-focused | Not suitable for portal pages, limited customization |
| Astro | SSG, 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.