← Writing

Four Seconds on a First Visit

4 min read#Networking#Deployment

The site always felt fast to me because my browser had a cache. A cold request from Beijing took 1.3 seconds to first byte and four seconds to finish, once twenty-four. On a small server in Hong Kong it now finishes in 0.3.

Table of Contents

This site is my résumé. It is static, with no database, and it used to sit on a free CDN’s static hosting. It always opened quickly for me, almost instantly — in hindsight that was just the cache: scripts, styles and HTML all stored locally.

The real problem only shows up with a cold request. A first visit from Beijing took 1.3 seconds to first byte and four seconds to finish, with a worst case of twenty-four. A 360 KB script delivered zero bytes in twenty-five seconds, and the stylesheet stalled halfway. The edge node serving me was in Los Angeles. Six overseas nodes, measured at the same time, answered in 0.2–0.5 seconds.

The conclusion was not that the code was slow. The free CDN had no presence in mainland China, so traffic crossed the border, and once packets started dropping the page fell apart. For a site read mostly from China, that is not an optimization problem; it is whether the site opens at all.

01Choosing something workable

The options were these. A mainland server has the lowest latency but requires a filing process — paperwork, review, identity details. A small machine in Hong Kong needs no filing and sits physically close. Cheaper niche hosts are tempting on price but impossible to verify for reliability. A machine at home was considered too: it has no public address, and a tunnel just sends the traffic back across the border.

I went with a 2-core, 2 GB machine in Hong Kong at a few dozen yuan a month. Ping from Beijing is 45–53 ms with no loss, and measured from a machine inside China it was already fast enough. Saving twenty yuan was not worth betting on a host I could not assess.

02The move

The site was already prerendered at build time, so the move mostly meant syncing the output. The server runs one lightweight web server: it obtains and renews certificates, redirects HTTP to HTTPS, maps /about to about.html, serves a 404 page for unknown paths, gives static assets a year of immutable caching and revalidates HTML on every visit.

Some basic hardening came with it: only ports 22, 80 and 443 open, key-only SSH, automatic bans after failed logins, unattended system updates. The machine does this one job.

Publishing is one command: build locally, then sync the output to the server. The build includes bilingual completeness and type checks, and nothing ships if either fails.

The domain now only resolves; it no longer goes through the CDN’s proxy. Otherwise traffic would still be sent to an overseas edge and the move would accomplish nothing.

One dependency needed patching: page views were read from and written to the hosting platform’s key-value store, and that endpoint returned 404 once the site moved. The fix was to reverse-proxy /api/* from the server back to the original platform. The front end did not change a line, and the counter kept climbing.

03Results

From Beijing: 0.22–0.29 s to first byte, 0.30–0.39 s for the full page, 0.51 s for the 147 KB script, about 0.7 s cold. Fifteen overseas nodes all returned 200: Southeast Asia 0.18–0.21 s, the US east coast 0.65 s, Europe 0.6–0.9 s, Brazil 1.16 s.

The costs are real. A single Hong Kong origin is a few hundred milliseconds slower than a global CDN for Europe and South America; assets are cached for a year, so repeat visits hide it. The old platform’s built-in DDoS scrubbing is gone. On a purely static site an attack mostly costs bandwidth, which is acceptable, and a mirror stays on the old platform: if something goes wrong, switching DNS moves traffic back.

One operational cost remains: pushing is not deploying. A push only updates the mirror; the live site needs the publish command run again. That note is in the repo’s operating manual so that, three months from now, I do not trip over it.

The lesson is one sentence: a cache on your own device lies about the real network. Measure from the audience’s network, with a cold request. Without that first bare request, this résumé would still go out with a four-second first visit.

Written byShihao Qi