Keeps the measurements, the implementation notes and the hosting comparison next to the code they justify.
9.6 KiB
Hosting evaluation: retain Netlify pending deployed evidence
Date: 15 September 2026. This is an interim decision report. Do not cut over to Cloudflare yet. The application improvements and selectable preview build are implemented, but the agreed real-host comparison cannot be completed with the current access and compatibility state.
Evidence and decision
| Item | Observed result | Consequence |
|---|---|---|
| Production Netlify site | livingdextracker, www.dextracker.uk; published revision inspected: 31fd959350962c1f1b973a06ecd5cf4eeef86514 |
This is the existing deployment, not this working tree |
| Function/database regions | Netlify us-east-1; linked Supabase eu-north-1; production public database URL matches that project |
Cross-region round trips are a plausible contributor; their latency share is unmeasured |
| Node production checks | Compact SSR, details, status writes, offline behavior and rendering budgets exercised locally | Application evidence only |
| Cloudflare build | Adapter 7.2.9, Wrangler 4.131.2, compatibility date 2026-09-15, nodejs_compat; build and dry-run passed |
Packaging is viable |
| Final Worker artifact | Approximately 1,876 KiB uncompressed / 336 KiB gzip; 9,621 static assets | Within published size/count limits; re-audit after renderer changes |
| Actual local workerd | Home, authenticated dex and public share page returned 200 | Basic runtime support demonstrated |
| Share image in workerd | /shared/:token/preview.png returned 500; sharp native module dynamic require is unsupported |
Compatibility gate failed; see separate renderer proposal below |
| Remote preview access | Netlify CLI authenticated; Wrangler not authenticated; equivalent staging fixtures/accounts not supplied | No paired deployed samples collected |
No production deployment, DNS change, database migration or region change was performed. Retaining Netlify now is a decision under incomplete evidence and a failed compatibility gate, not evidence that Netlify is faster.
Netlify supports streaming; the investigation's buffering describes the installed adapter/runtime combination, not the entire platform. Cloudflare's supported SvelteKit adapter and partial Node compatibility do not imply support for native sharp. Sources: Netlify streaming, SvelteKit Cloudflare adapter, Cloudflare Node compatibility.
Build and environment requirements
- Default builds remain Netlify.
NODE_ADAPTER=trueorDEPLOY_TARGET=nodeselects Node.npm run build:cloudflareselects Workers with Static Assets;npm run check:cloudflarechecks packaging;npm run preview:cloudflareruns workerd locally. Do not run builds concurrently. - Cloudflare uses platform compression rather than the Node compression implementation. Verify actual deployed content encoding for HTML, SvelteKit data and JSON responses.
- Supply public Supabase URL/anon key at build time, and private Supabase/export/OAuth secrets through platform bindings. Preview secrets must belong to the same staging project on both hosts. Keep provider redirect allowlists and cookie/domain settings explicit.
- Authenticate Wrangler and select the intended account before creating a remote preview. Build/dry-run success does not authorize production routing.
- Current documented limits include 128 MB memory, one-second startup, 64 MiB uncompressed Worker size and 20,000/100,000 static files on Free/Paid. Each static file must be at most 25 MiB. Free CPU allowance is only 10 ms per invocation; do not assume the app or renderer fits it. Measure CPU/memory/startup under actual workerd and deployment. Workers limits
Separate proposal: Worker-compatible share-image renderer
This is the concrete implementation proposal required by the migration gate. It is not implemented in this change.
- Extract the existing SVG construction, XML escaping and text truncation into a platform-neutral module. Preserve the endpoint, 1,200 × 630 dimensions, progress values, privacy behavior and cache headers.
- Keep a Node renderer using
sharp. Add a Cloudflare renderer using@resvg/resvg-wasm, selected through the same build-time platform alias pattern as compression. Pin a verified version in the renderer change; exclude nativesharpfrom the Worker dependency graph. - Bundle the renderer's WASM as a module and initialize lazily once per isolate through a shared promise. Import a compiled WebAssembly module using the supported Workers approach, rather than assuming a browser-style URL fetch loader works. Release per-render allocations after producing a PNG byte array. Workers WASM support, resvg WASM package
- Bundle licensed regular/bold fonts, for example Noto Sans with its license. Explicitly map SVG weights to those fonts; do not rely on operating-system fonts. Verify accented Pokémon names, punctuation, long descriptions and the intended non-Latin fallback policy. Font appearance will need review against existing PNGs.
- Add golden-image/metadata tests for empty/long names, XML special characters, all badges and 0/100% progress. Exercise concurrent requests, first invocation, repeated renders and error recovery in built workerd. Require HTTP 200, PNG signature/dimensions, correct sharing access and no private notes.
- Re-audit bundle/assets, CPU, memory and startup after adding WASM/fonts. Run the deployed share-image test and backup/export/auth matrix before setting compatibility to passed. If this renderer exceeds operating constraints, compare a separately hosted image service in a new proposal; do not silently redirect rendering to production Netlify.
Remaining deployed benchmark
- Supply equivalent staging credentials and disposable national/scoped-form fixtures, then publish this exact code to both preview hosts. Preserve actual deployed revision and environment metadata.
- Establish improved Netlify document/data baselines. Consider a separate region-aligned Netlify preview as an additional experiment; do not change the primary paired comparison mid-run.
- Exercise sign-in, token refresh, account switching, Google/Dropbox OAuth and refresh, export generation, public shares/images, compression, secrets/bindings, static cache headers, service-worker install/update/offline routing and custom preview domains on both real hosts.
- Use the comparison harness for at least 30 warm samples per host × fixture × direct/client navigation. Keep code, Supabase project, fixtures, browser settings and client locations identical. Run additional deliberately idled samples and record the idle duration separately; first-observed is not a cold-start claim.
- Recommend migration only when overall first-interactive p75 improves by both 20% and 200 ms, no fixture/navigation combination regresses by more than 10%, and functional checks pass. Include costs and operating requirements before making the final decision.
Cost projection and operations
Actual monthly traffic, CPU usage and the account's current Netlify billing arrangement are not available, so no savings claim is justified.
Cloudflare Paid has a $5/month minimum, includes 10 million dynamic requests and 30 million CPU-ms, then charges $0.30/million requests and $0.02/million CPU-ms. Direct static asset requests are free; requests served through Workers Caching have different billing. Illustrative direct-static configuration: 1 million dynamic requests at 20 ms CPU each remains within $5; 10 million at 20 ms is approximately $8.40. These exclude other services and are assumptions, not measured app CPU. Workers pricing
For credit-based Netlify plans, estimate credits as 15 × production deploys + 10 × compute GB-hours + 20 × bandwidth GB + 2 × requests/10,000. Confirm whether this account uses credit-based or legacy billing and apply its actual plan/allowance. Thumbnail byte savings affect bandwidth, while increased first-party sprite requests affect request metering. Netlify pricing
Before a qualifying cutover: move database migrations out of host builds into one serialized CI release step; provision secrets and OAuth redirects; validate the custom domain and cookies; monitor error/latency/export metrics; retain the working Netlify deployment and a documented DNS rollback. These production changes remain conditional on completing the gates above.