From a53ff4da19bfa6dad3594829eeaec3b42759c810 Mon Sep 17 00:00:00 2001 From: raj-khan Date: Wed, 7 Oct 2026 17:38:25 +0800 Subject: [PATCH] Fix the site root serving the editor instead of the landing page Vercel serves a file before any rewrite, so dist/index.html answered / and the rewrite to landing.html never applied. The editor is now built as app.html, and /new and /flow rewrite to it. CI fails if an index.html comes back. --- .github/workflows/ci.yml | 5 +++- README.md | 6 ++-- index.html => app.html | 0 .../fl-97 - Landing-page-at-the-site-root.md | 30 +++++++++++++++++++ docker/nginx.conf | 4 +-- src/pwa/__tests__/serviceWorker.spec.js | 8 ++--- src/pwa/serviceWorker.js | 2 +- vercel.json | 8 ++--- vite.config.js | 11 +++++-- 9 files changed, 58 insertions(+), 16 deletions(-) rename index.html => app.html (100%) create mode 100644 backlog/tasks/fl-97 - Landing-page-at-the-site-root.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b76091c..c13757d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,12 @@ jobs: if: '!cancelled()' run: npm run test:coverage + # Vercel serves a file before any rewrite, so a dist/index.html would take / from the landing. - name: Build if: '!cancelled()' - run: npm run build + run: | + npm run build + test -f dist/landing.html && test -f dist/app.html && test ! -e dist/index.html # The examples are Flow's own diagrams: they must parse, and the SVGs next # to them must be what the renderer draws today. diff --git a/README.md b/README.md index 5effa1a..ec6c7a0 100644 --- a/README.md +++ b/README.md @@ -498,8 +498,10 @@ production build in another. ## Deployment -The build is a static single page app, so any static host works as long as unknown paths fall back -to `index.html`. `docker/nginx.conf` does that for the container and `vercel.json` for Vercel. +The build is static, so any static host works as long as `/` serves `landing.html` and the +editor's routes (`/new`, `/flow` and below them) fall back to `app.html`. There is deliberately no +`index.html`: Vercel serves a file before any rewrite, so one would take `/` from the landing. +`docker/nginx.conf` does this for the container and `vercel.json` for Vercel. ## Known limits diff --git a/index.html b/app.html similarity index 100% rename from index.html rename to app.html diff --git a/backlog/tasks/fl-97 - Landing-page-at-the-site-root.md b/backlog/tasks/fl-97 - Landing-page-at-the-site-root.md new file mode 100644 index 0000000..cc8960a --- /dev/null +++ b/backlog/tasks/fl-97 - Landing-page-at-the-site-root.md @@ -0,0 +1,30 @@ +--- +id: FL-97 +title: Landing page at the site root +status: Done +assignee: [] +created_date: '2026-10-07 09:35' +updated_date: '2026-10-07 09:38' +labels: + - bug +dependencies: [] +priority: high +ordinal: 27000 +--- + +## Description + + + +isketch.online/ serves the editor, not the landing page: Vercel serves dist/index.html for / before the rewrite to /landing.html applies. Build the editor as app.html so / falls through to the landing, and /new and /flow rewrite to the app. + + +## Acceptance Criteria + + + +- [x] #1 isketch.online/ shows the landing page +- [x] #2 /new, /new/node/:id and /flow still open the editor, in Vercel, nginx, vite dev and preview +- [x] #3 The service worker still opens the app offline + + diff --git a/docker/nginx.conf b/docker/nginx.conf index d60f637..8b6ca69 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -11,7 +11,7 @@ server { # Client-side routes have no file on disk; without this a refresh returns 404. location / { - try_files $uri $uri/ /index.html; + try_files $uri $uri/ /app.html; } location ~* \.(js|css|svg|png|jpg|jpeg|gif|ico|woff2)$ { @@ -30,7 +30,7 @@ server { } # Not cached, or a deploy stays invisible until browsers drop their copy. - location = /index.html { + location = /app.html { add_header Cache-Control "no-cache"; } diff --git a/src/pwa/__tests__/serviceWorker.spec.js b/src/pwa/__tests__/serviceWorker.spec.js index 91d8159..56a2628 100644 --- a/src/pwa/__tests__/serviceWorker.spec.js +++ b/src/pwa/__tests__/serviceWorker.spec.js @@ -5,12 +5,12 @@ import { PUBLIC_SHELL, serviceWorkerSource, shellFiles } from '../serviceWorker. describe('shellFiles', () => { it('lists the build and the public shell once each, without maps or itself', () => { const files = shellFiles([ - 'index.html', + 'app.html', 'assets/index-abc.js', 'assets/index-abc.js.map', 'sw.js', ]) - expect(files).toContain('/index.html') + expect(files).toContain('/app.html') expect(files).toContain('/assets/index-abc.js') expect(files).not.toContain('/assets/index-abc.js.map') expect(files).not.toContain('/sw.js') @@ -20,11 +20,11 @@ describe('shellFiles', () => { }) describe('serviceWorkerSource', () => { - const source = serviceWorkerSource('v1', ['/index.html', '/assets/a.js']) + const source = serviceWorkerSource('v1', ['/app.html', '/assets/a.js']) it('names its cache after the build, and caches the shell on install', () => { expect(source).toContain('const CACHE = "isketch-v1"') - expect(source).toContain('const SHELL = ["/index.html","/assets/a.js"]') + expect(source).toContain('const SHELL = ["/app.html","/assets/a.js"]') expect(source).toContain('cache.addAll(SHELL)') }) diff --git a/src/pwa/serviceWorker.js b/src/pwa/serviceWorker.js index c46450a..e8c45c7 100644 --- a/src/pwa/serviceWorker.js +++ b/src/pwa/serviceWorker.js @@ -17,7 +17,7 @@ export function serviceWorkerSource(version, files) { return `// isketch service worker, build ${version}. Written by the build; do not edit. const CACHE = ${JSON.stringify(`isketch-${version}`)} const SHELL = ${JSON.stringify(files)} -const APP = '/index.html' +const APP = '/app.html' // A module import sends Origin and the install did not; the names are hashed, so Vary is moot. const MATCH = { ignoreVary: true } diff --git a/vercel.json b/vercel.json index 3549e85..b93f937 100644 --- a/vercel.json +++ b/vercel.json @@ -2,9 +2,9 @@ "$schema": "https://openapi.vercel.sh/vercel.json", "rewrites": [ { "source": "/", "destination": "/landing.html" }, - { "source": "/new", "destination": "/index.html" }, - { "source": "/new/:path*", "destination": "/index.html" }, - { "source": "/flow", "destination": "/index.html" }, - { "source": "/flow/:path*", "destination": "/index.html" } + { "source": "/new", "destination": "/app.html" }, + { "source": "/new/:path*", "destination": "/app.html" }, + { "source": "/flow", "destination": "/app.html" }, + { "source": "/flow/:path*", "destination": "/app.html" } ] } diff --git a/vite.config.js b/vite.config.js index 8861bf6..d2100d2 100644 --- a/vite.config.js +++ b/vite.config.js @@ -9,9 +9,12 @@ import tailwindcss from '@tailwindcss/vite' import { analyticsScript } from './src/api/analytics.js' import { serviceWorkerSource, shellFiles } from './src/pwa/serviceWorker.js' -/** Where links point when the head is built; %SITE_URL% in index.html. */ +/** Where links point when the head is built; %SITE_URL% in app.html. */ const SITE_URL = (process.env.VITE_SITE_URL ?? 'https://isketch.online').replace(/\/+$/, '') +/** The editor's routes, served app.html as vercel.json and nginx do. */ +const APP_ROUTE = /^\/(new|flow)(\/|$)/ + /** Serve the landing at / and content pages at their clean URL, as production does. */ function serveLanding(server) { server.middlewares.use((req, res, next) => { @@ -20,6 +23,10 @@ function serveLanding(server) { req.url = '/landing.html' + url.search return next() } + if (APP_ROUTE.test(url.pathname)) { + req.url = '/app.html' + url.search + return next() + } // A directory with an index, such as /docs/format: its page, not the SPA. const hasIndex = !url.pathname.endsWith('/') && @@ -103,7 +110,7 @@ export default defineConfig({ build: { rollupOptions: { input: { - index: fileURLToPath(new URL('./index.html', import.meta.url)), + app: fileURLToPath(new URL('./app.html', import.meta.url)), convert: fileURLToPath(new URL('./convert.html', import.meta.url)), }, },