A static React portfolio: building pages and deploying to Pages
Notes from building this bilingual portfolio: static rendering, a smaller JavaScript bundle, and two deployment configuration mistakes.
Rendering at build time #
This portfolio presents a profile, projects, and technical notes. Its content does not require authentication or live requests. React organizes the components; the build generates HTML that Cloudflare Pages can serve directly.
The build runs TypeScript checks, bundles browser assets, bundles the rendering module, and writes each route as a static page. Only dist is deployed. The dist-ssr directory is a build input, not a backend that runs on the host.
English and Chinese homepages live at / and /zh/. Markdown notes are validated during the build, including their title, date, language, category, and draft status. Drafts are excluded from public lists and the sitemap.
Keeping React out of the browser bundle #
The initial implementation hydrated the page with React. The interactions were modest: tracking the current section, preserving that section when switching languages, moving focus after navigation, and closing disclosures with Escape.
These enhancements now use a small TypeScript module. React still generates the pages at build time, but the browser does not download its runtime or the complete collection of note data. Native details elements keep the menu and conceptual agent nodes usable without JavaScript.
Before this first note was added, the six-page build emitted 1362 bytes of gzipped JavaScript, down from approximately 78.10 KB. CSS measured 3702 bytes gzip. These are compressed build-file totals, not mobile loading measurements or evidence that LCP, CLS, and INP targets have been met.
Two deployment mistakes #
The first attempt used the Workers creation flow. Compilation succeeded, but the subsequent npx wrangler deploy command reached an interactive configuration prompt. Switching to the Pages Git import flow, with npm run build and output directory dist, completed the deployment.
The second issue was less visible. The repository used master, while the indexing check expected main. Pages loaded successfully, but production HTML contained noindex, robots.txt blocked crawling, and the sitemap was empty. The fix aligned both the indexing check and the statistics workflow with the actual production branch.
The build also needs the real public origin in SITE_URL. It is used for canonical URLs, social images, and the sitemap. Without it, this project falls back to a local address and disables indexing.
Verification remains part of maintenance #
npm test builds the site, runs automated tests, and checks local links, resources, and size budgets. A separate job refreshes GitHub statistics and retains the previous snapshot on failure, so normal builds do not depend on an API request.
Deployment checks also need HTTP status codes, caching, compression, narrow layouts, and keyboard navigation. Real-device performance and assistive-technology checks remain separate work. A successful deployment confirms that files were published; it does not by itself verify the content, indexing policy, or update process.
Run these commands to check and preview the site locally:
npm run test
npm run preview