This post documents how HarnessDo.com went from zero to live — built and deployed by DeepSeek Harness, with me deciding and reviewing. A memo for myself, and hopefully a useful reference for anyone building a similar bilingual blog.
Who built it: DeepSeek Harness and me
The code, deployment, and troubleshooting were almost entirely done by DeepSeek Harness (DSH) — an AI coding agent. As the human, I made the decisions, supplied accounts and credentials, and reviewed every result. Splitting the labor makes it clear what an AI agent actually takes off your hands.
What DeepSeek Harness did
- Built the Astro 7 bilingual project from scratch: config, i18n routing, content-collection schema, layouts/components/styles
- Fixed environment issues: npm/pnpm cache EPERM, Astro telemetry sandbox block
- Designed the bilingual content collections and fixed the id-collision bug for same-slug zh/en posts
- Wrote the first bilingual posts (drafts)
- Built, previewed, and verified every route locally
- Created the Cloudflare Pages project and deployed it
- Verified domain ownership via RDAP/DNS (caught the harnessgo.com vs harnessdo.com mismatch)
- Renamed everything harnessgo → harnessdo (repo, Pages project, working directory)
- Attached the custom domain, created the CNAME, and followed SSL issuance until https worked
- Installed Cloudflare skills and MCP servers for DSH itself, and restarted the service
- Renamed the visible brand to HarnessDo.com and redeployed
- Git init and first commit
What I (the human) did
- Registered the domain (harnessdo.com, at Cloudflare)
- Made the stack decisions: Astro, bilingual zh+en, deploy to Cloudflare Pages
- Completed the Cloudflare OAuth login in the browser
- Created and configured Cloudflare API tokens (including DNS edit)
- Corrected course each round: confirmed harnessdo, asked for the full rename, chose the HarnessDo brand
- Provided the launch command and confirmed the restart
- Ongoing: write posts, review content, maintain the site
In one line: DSH did nearly all the hands-on execution; I did the deciding, authorizing, and accepting. That is exactly the DeepSeek Harness usage this blog tracks.
Stack
| Need | Choice | Why |
|---|---|---|
| Static site framework | Astro | Built for content sites; Markdown is a first-class citizen; outputs pure static pages with zero JS by default |
| Hosting | Cloudflare Pages | Free, global CDN, same account as the domain, no ICP filing |
| Writing format | Markdown | Content collections with schema validation — the cheapest thing to maintain long-term |
Astro over Next.js or Hugo, mainly because of the content model: content collections validate frontmatter against a schema at build time, so a malformed post fails the build instead of silently breaking the site. That matters for a long-lived bilingual blog.
Project structure
HarnessDo.com/
├── astro.config.mjs # site config + sitemap integration
├── src/
│ ├── content.config.ts # content collection schema (Astro 5+ style)
│ ├── content/blog/
│ │ ├── zh/*.md # Chinese posts
│ │ └── en/*.md # English posts
│ ├── i18n/ui.ts # bilingual UI strings
│ ├── layouts/ # Base / PostLayout
│ ├── components/ # Header / Footer
│ └── pages/
│ ├── index.astro # root: client-side language redirect
│ ├── [lang]/ # /zh/ /en/ homes
│ │ ├── blog/ # post list + [...slug] post pages
│ │ ├── about.astro
│ │ └── rss.xml.ts # one RSS feed per language
│ └── 404.astro
└── public/ # favicon, robots.txt
How the bilingual part works
- Routes are
/zh/...and/en/...; the root/redirects client-side based onnavigator.language(defaults to Chinese). - Each post is written twice under the same slug:
src/content/blog/zh/<slug>.mdandsrc/content/blog/en/<slug>.md, withlangdeclared in frontmatter. - Each file’s id keeps the language prefix (e.g.
zh/2025-08-15-welcome) to avoid collisions between same-slug posts; the URL slug strips the prefix at the route layer (slugOfinsrc/lib/posts.ts). - UI strings (nav, buttons, about page) live in
src/i18n/ui.ts, looked up bylang.
Deployment
The build output is dist/, pushed to Cloudflare Pages with wrangler:
pnpm build
wrangler pages deploy dist --project-name=harnessdo
Domain binding: Cloudflare dashboard → Pages → project → Custom domains, add HarnessDo.com, then add a CNAME record (@ → harnessdo.pages.dev, proxied). The domain is registered at Cloudflare, so no ICP filing.
How to publish a new post
- Write
<date>-<slug>.mdinsrc/content/blog/zh/, and the English version inen/, keeping the same slug. - Required frontmatter:
lang,title,description,pubDate. Optional:tags,draft,dshVersion,series. - Preview locally with
pnpm dev, thenpnpm build && wrangler pages deploy dist --project-name=harnessdoto ship.
The schema lives in src/content.config.ts; new fields are added there.
Pitfalls hit along the way
- Broken npm cache.
~/.npmon this machine contains root-owned files, so npm fails with EPERM. Fix:.npmrcpointscacheat the project directory, and commands override the inheritednpm_config_cacheenv var (env beats project config). - pnpm’s dlx cache lives in the home dir too.
pnpm create astro’s temp cache was blocked as well, so I hand-wrote every file — which ended up being clearer anyway, since each config is now understood.
That’s it. On to the actual writing.