Docs
Browse documentation

Deployment and production preview

On this page

The site is prerendered by Angular and hosted through Cloudflare Workers Static Assets. Documentation is built and deployed alongside the blog, with the same shell, theme, origin, and URL conventions.

Build and inspect

bash
pnpm build
pnpm preview

The build generates source-derived modules first, prerenders routes, then prepares deployment metadata. The Node preview serves dist/blog/browser at http://127.0.0.1:4173/ by default. It is useful for inspecting the production application but cannot reproduce every Cloudflare asset redirect.

Postbuild metadata preparation scans only each prerendered document's head. Do not construct a full DOM merely to read canonicals, robots directives, dates, or Markdown discovery links: static formula markup makes article bodies large, and retaining their DOM representation can exhaust a constrained build heap.

For actual Worker/static-asset behavior after building:

bash
pnpm preview:cloudflare

This starts local Wrangler at http://127.0.0.1:8787/. Use the Cloudflare Playwright project to check route handling and HTTP behavior. Do not infer successful publication from a local preview: production changes become public only through the repository's configured deployment process.

Hashed lazy chunks from an older deployment can disappear while a reader keeps that version of the application open. The client recognizes the browser's dynamic-import failure and performs one full navigation to the requested page so the new document loads the current asset graph. It rate-limits recovery per URL in session storage and retains the visible manual retry when reloading is unsafe or fails again.

Build outputs and ownership

Output Produced from
HTML routes Angular client/server routes and generated content metadata
404.html The prerendered /404 page
_redirects configs/redirects.yaml
sitemap.xml, robots.txt Prerender manifest, canonical URLs, and noindex metadata
feed.xml, atom.xml Published article summaries and authored dates
Markdown exports and llms.txt The same prose as HTML articles, CV, directories, and documentation
dist/blog/markdown-routes.json, worker.mjs Prerendered Markdown alternate links and the shared request handler

Never hand-edit generated files. The Worker entry and its map stay outside public static assets. A build validates that every advertised Markdown alternate exists.

Postbuild appends one-year immutable browser-cache rules to the copied _headers for Angular's hashed root main-*.js, chunk-*.js, styles-*.css, and media/* assets. These namespaces are reserved for build output; postbuild rejects unversioned files within them. Keep authored downloads under /posts or /assets. HTML, Markdown, and unversioned article images retain their existing cache policy. The negotiated page Worker still owns Vary: Accept and private revalidation. Verify cache headers through the Cloudflare preview, since the Node preview does not apply _headers. A deployment is required for production headers to change.

URLs, redirects, and missing pages

Canonicals use slashless URLs such as /blog/notes/example and /docs/development. Cloudflare assets.html_handling is drop-trailing-slash, so these canonical URLs must return 200 directly. Check direct visits as well as Angular navigation when adding a route.

Prerender every article, ancestor category, archive page, and documentation page from the same metadata used by the router. Redirect pages need prerendering when the static host must handle direct visits. Use the shared 404 experience for unknown paths, with noindex metadata and an actual HTTP 404 from the production host.

Sitemap generation excludes redirect and noindex pages. Article lastmod comes from updated ?? date; a build timestamp is not a content modification date. Documentation has no invented publication/update timestamps, so it has no article dates or BlogPosting structured data.

Private article access

private: true removes an article from public discovery but still prerenders its direct HTML route. Before publishing one, create a Cloudflare Access self-hosted application for pufanyi.com/blog/<slug>* with an Allow policy for the exact owner email. The trailing wildcard covers the canonical route, slash and HTML aliases, and Markdown URL. Verify anonymous requests to each return an Access login redirect, then confirm the owner can read the article after login. Protect post-local assets separately when a private article has any. Keep the source repository private if the authored prose itself must remain private.

Markdown negotiation and discovery

Every handbook page advertises its Markdown alternative. /docs uses /docs/index.md; other pages use /docs/<slug>.md. Article exports use /blog/<slug>.md; archive pages share /blog/index.md; homepage and CV share /profile.md.

bash
curl -H 'Accept: text/markdown' http://127.0.0.1:8787/docs/development

Only an explicit preferred text/markdown accepts Markdown. Ordinary browser requests receive HTML. Negotiated responses use Vary: Accept and avoid shared caching keyed only by URL; the underlying HTML and Markdown assets retain separate caches. One-to-one article and documentation exports identify their HTML canonical, including direct GET/HEAD and conditional 304 responses. Shared archive and profile exports are not canonicalized to partial pages.

Keep wrangler.jsonc worker-first coverage and public/_headers MIME rules aligned for /blog/* and /docs/*. Missing .md URLs must return an HTML 404 with the correct MIME type; Cloudflare can otherwise apply the requested URL's Markdown header to its HTML fallback. PDF viewer .mjs and .wasm files need JavaScript and WebAssembly MIME types in preview too.

Release checks

Run check and noninteractive unit tests after the final change, then build and run the relevant production browser tests. Verify direct URLs, canonicals, internal documentation links, Markdown responses, feeds, and the custom 404. Review the actual source diff before pushing. Credentials and production deployment access belong to the existing repository environment; never add them to public documentation or static assets.

The production Worker disables its default workers.dev hostname. Keep traffic on the configured custom domain so path-scoped Cloudflare Access applications cannot be bypassed through the otherwise public Worker subdomain.