跪拜 Guibai
← All articles
Frontend

Seven Frontend Traps From a 37-Day Anonymous Chat Site

By 857god ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Each failure can cost hours or corrupt production data, and most come from configs copied without understanding their full consequences. For any developer shipping a pre-rendered SPA, these are the exact edge cases that will bite when local dev hides the truth.

Summary

857god ran a static pre-rendered SPA (Vite + React, served by nginx) for 37 days and collected seven distinct frontend failures that all trace back to trusting the local dev environment or hand-copied configuration. The worst was nginx's catch-all `try_files` rewrite, which returned 200 and the index.html for any random path, making the site look like it had unlimited pages to search engines.

Other failures came from naive string replacement in the pre-render script: a title containing a double quote prematurely closed the `content` attribute on the meta description, silently producing empty descriptions that Bing flagged. Hardcoding `og:type` as `website` took a minute to fix, but only if you knew to look. Native ESM refusing to resolve extension-less imports broke a node script, and a deployment command with `rsync --delete` removed the admin panel, an APK download directory, and `.well-known/` in one shot.

The post ends with useful habits: escape every attribute value, always use function callbacks for `String.replace`, always dry-run rsync before syncing, and keep the route list in a single source so the nginx whitelist stays in sync. The site now runs 55 pre-rendered pages, but remains entirely image-free because the developer couldn't decide on a non-embarrassing image strategy.

Takeaways
— Using `try_files $uri $uri/ /index.html` as a catch-all makes every unknown URL return 200 with the full index.html, wasting crawl budget and telling Google the site has infinite pages.
— Fixing the 404 problem required a whitelist regex for real SPA routes and a separate `location /` block returning 404, plus an error_page for /404.html.
— String-replacing the description meta with a title containing a double quote closes the `content` attribute early, resulting in an empty meta value that browsers read as empty.
— All HTML attribute values should be escaped with `escAttr` (compensating `&`, `"`, `<`, `>`) before injection into templates.
— `og:type` should be `article` for blog posts, not `website`, and should include `article:published_time` for time-sensitive indexing.
— Native ESM does not resolve file extensions; `node` fails with `ERR_MODULE_NOT_FOUND` on extension-less imports, so build scripts should always write the `.js` suffix.
— Running `rsync -a --delete dist/ /www/wwwroot/site/` deletes any target-directory files that don't exist in `dist/`, including `/admin/`, `/download/`, and `/.well-known/`.
— Always run `rsync --dry-run` first to see what will be changed or deleted before the real sync.
— `String.replace` with a replacement string treats `$&`, `$'`, and `` $` `` as group references; using a function replacement avoids the risk entirely.
— A hardcoded nginx route whitelist breaks when new SPA routes are added; keeping all routes in one JS file and grepping the config catches the mismatch.
Conclusions

The pattern of failures shows that local development and build tools hide real-world edge cases: Vite resolves extensions, the browser ignores malformed meta, and nginx's catch-all returns 200 for everything, so none of these issues show up until production.

The meta description bug is the most instructive: the error message Bing gives ("too short") points away from the real cause, and the author lost a day because the symptom was too specific.

The rsync --delete accident is a reminder that deployment commands are as dangerous as code, and the lack of a dry-run is not a micro-optimization but a safety check.

Pre-rendering with fragment-based string replacement is fragile; using a proper template engine or DOM parser would have prevented two of the seven pitfalls.

Concepts & terms
try_files
nginx directive that tries file paths in order; falling back to /index.html on failure is how SPAs hand routing to the client, but it also serves index.html for any path, including garbage.
rsync --delete
rsync flag that deletes files in the destination not present in the source, ensuring exact mirroring. It's destructive when the source isn't a complete representation of the target.
Native ESM
The browser-standard ES module system in Node.js resolves imports strictly, without adding file extensions like .js automatically, unlike bundlers such as Vite/esbuild.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗