Production: direct URL hits and refresh on sub-routes return raw "Not Found" (missing SPA fallback) #196

Closed
opened 2026-05-12 00:13:28 +00:00 by Brouie · 2 comments
Brouie commented 2026-05-12 00:13:28 +00:00 (Migrated from gitlab.com)

Summary

Direct URL hits to any sub-route on production return raw Not Found server text instead of the SPA. Only / (root) renders the app correctly. Page refresh on any route, deep links shared externally, and bookmarks to sub-routes are all broken.

What is wrong

Production static host has no SPA fallback configured. When a browser requests yieldomega.com/timecurve directly, the server looks for a literal /timecurve file on disk, doesn't find one, returns a server-level 404 with body Not Found. React Router never gets a chance to handle the route because the SPA bundle is never served.

In-app navigation works fine because React Router intercepts the click client-side and updates the URL via History API without a server roundtrip.

Steps to reproduce

  1. Fresh incognito Chrome, no extensions.
  2. Paste https://yieldomega.com/timecurve directly into address bar, hit Enter.
  3. Observed: raw Not Found text top-left, no app shell, no nav, no branding.
  4. Repeat with /vesting, /referrals, /timecurve/arena, any non-root path — same result.
  5. Alternative repro: load https://yieldomega.com/ (root, works fine), click TimeCurve nav button, URL updates to /timecurve and page renders correctly. Hit F5 to refresh. Page immediately flips to raw Not Found.

User impact

  • Any shared link to a sub-route breaks for every recipient (Twitter, Discord, Telegram).
  • Browser tab discard + return = broken (Chrome auto-discards inactive tabs, reloads from URL on focus).
  • Refresh on any sub-route = broken.
  • Bookmarks to sub-routes = broken on next visit.
  • SEO: every sub-route indexes as a 404.
  • Wallet redirect/callback URLs that land on a sub-route = broken connect flow.

Add an SPA fallback rewrite rule at the static host. The pattern is "any unmatched path serves /index.html with 200, not 404." Host-specific examples:

Render (render.yaml at repo root, or dashboard → Redirects/Rewrites):

services:
  - type: web
    env: static
    routes:
      - type: rewrite
        source: /*
        destination: /index.html

Vercel (vercel.json):

{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }

Netlify / Cloudflare Pages (public/_redirects):

/*  /index.html  200

nginx:

location / {
  try_files $uri $uri/ /index.html;
}

Verification checklist (post-fix)

  • Direct URL hit to https://yieldomega.com/timecurve (fresh tab) renders the TimeCurve view with full app shell.
  • F5 refresh on /timecurve/arena reloads the arena view, doesn't flip to Not Found.
  • https://yieldomega.com/referrals direct paste renders the referrals page.
  • https://yieldomega.com/this-route-does-not-exist renders the SPA in-app 404 (React Router fallback), NOT the raw server 404 text. (If no in-app 404 exists, separate polish-nit can track that.)
  • Shared external link (paste /timecurve URL into a new incognito tab) lands on app shell, not raw text.
  • Network tab for sub-route hit returns 200 status with index.html content (not 404 with Not Found body).

Source

Discovered during #147 production visual QA walk, items 3.12 (Deep links), 4.4 (Vesting hidden-from-nav), 4.11 (404 / global error pages) all FAIL from this single root cause.

@PlasticDigits — flagging pre-launch since this affects every shared link, refresh, and bookmark. Single config change at the host. Happy to verify post-fix immediately.

## Summary Direct URL hits to any sub-route on production return raw `Not Found` server text instead of the SPA. Only `/` (root) renders the app correctly. Page refresh on any route, deep links shared externally, and bookmarks to sub-routes are all broken. ## What is wrong Production static host has no SPA fallback configured. When a browser requests `yieldomega.com/timecurve` directly, the server looks for a literal `/timecurve` file on disk, doesn't find one, returns a server-level 404 with body `Not Found`. React Router never gets a chance to handle the route because the SPA bundle is never served. In-app navigation works fine because React Router intercepts the click client-side and updates the URL via History API without a server roundtrip. ## Steps to reproduce 1. Fresh incognito Chrome, no extensions. 2. Paste `https://yieldomega.com/timecurve` directly into address bar, hit Enter. 3. Observed: raw `Not Found` text top-left, no app shell, no nav, no branding. 4. Repeat with `/vesting`, `/referrals`, `/timecurve/arena`, any non-root path — same result. 5. Alternative repro: load `https://yieldomega.com/` (root, works fine), click TimeCurve nav button, URL updates to `/timecurve` and page renders correctly. Hit F5 to refresh. Page immediately flips to raw `Not Found`. ## User impact - Any shared link to a sub-route breaks for every recipient (Twitter, Discord, Telegram). - Browser tab discard + return = broken (Chrome auto-discards inactive tabs, reloads from URL on focus). - Refresh on any sub-route = broken. - Bookmarks to sub-routes = broken on next visit. - SEO: every sub-route indexes as a 404. - Wallet redirect/callback URLs that land on a sub-route = broken connect flow. ## Recommended fix Add an SPA fallback rewrite rule at the static host. The pattern is "any unmatched path serves `/index.html` with 200, not 404." Host-specific examples: **Render** (`render.yaml` at repo root, or dashboard → Redirects/Rewrites): ```yaml services: - type: web env: static routes: - type: rewrite source: /* destination: /index.html ``` **Vercel** (`vercel.json`): ```json { "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] } ``` **Netlify / Cloudflare Pages** (`public/_redirects`): ``` /* /index.html 200 ``` **nginx**: ``` location / { try_files $uri $uri/ /index.html; } ``` ## Verification checklist (post-fix) - [ ] Direct URL hit to `https://yieldomega.com/timecurve` (fresh tab) renders the TimeCurve view with full app shell. - [ ] F5 refresh on `/timecurve/arena` reloads the arena view, doesn't flip to `Not Found`. - [ ] `https://yieldomega.com/referrals` direct paste renders the referrals page. - [ ] `https://yieldomega.com/this-route-does-not-exist` renders the SPA in-app 404 (React Router fallback), NOT the raw server 404 text. (If no in-app 404 exists, separate polish-nit can track that.) - [ ] Shared external link (paste `/timecurve` URL into a new incognito tab) lands on app shell, not raw text. - [ ] Network tab for sub-route hit returns 200 status with `index.html` content (not 404 with `Not Found` body). ## Source Discovered during #147 production visual QA walk, items 3.12 (Deep links), 4.4 (Vesting hidden-from-nav), 4.11 (404 / global error pages) all FAIL from this single root cause. @PlasticDigits — flagging pre-launch since this affects every shared link, refresh, and bookmark. Single config change at the host. Happy to verify post-fix immediately.
Brouie commented 2026-05-12 00:46:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #147

mentioned in issue #147
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-12 07:27:40 +00:00
PlasticDigits commented 2026-05-12 07:27:41 +00:00 (Migrated from gitlab.com)

@Brouie Due to missing rewrite rule on render - fixed.

@Brouie Due to missing rewrite rule on render - fixed.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#196
No description provided.