Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# Cloudflare
.wrangler
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "sveltekit-3-cloudflare-workers",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "wrangler dev --port 4173",
"proxy": "node start-event-proxy.mjs",
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"test:e2e": "playwright test",
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test:e2e"
},
"dependencies": {
"@sentry/sveltekit": "file:../../packed/sentry-sveltekit-packed.tgz"
},
"devDependencies": {
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@sveltejs/adapter-cloudflare": "next",
"@sveltejs/kit": "next",
"@sveltejs/vite-plugin-svelte": "^7.1.2",
"cookie": "^2.0.1",
"svelte": "^5.48.0",
"typescript": "^6.0.0",
"vite": "^8.0.0",
"wrangler": "^4.118.0"
},
"volta": {
"node": "22.20.0",
"extends": "../../package.json"
},
"sentryTest": {
"optional": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

// The bug this app guards against only reproduces against the *built* Worker:
// `@sveltejs/adapter-cloudflare`'s dev platform proxy still sets the legacy
// `platform.context` alias, while the emitted worker only sets `platform.ctx`.
// So we serve the build output with `wrangler dev` rather than running `vite dev`.
const config = getPlaywrightConfig({
startCommand: 'pnpm preview',
port: 4173,
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// `App.Platform` (including `platform.ctx`) is declared by `@sveltejs/adapter-cloudflare`.
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
}
}

export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="off">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineEnvVars } from '@sveltejs/kit/env';

// SvelteKit 3 makes "explicit environment variables" the default and removes the
// legacy `$env/*` virtual modules. Declared vars are imported from `$app/env/private`
// (server only) and `$app/env/public` (client-safe).
export const variables = defineEnvVars({
// `static: true` inlines the value at build time. On Cloudflare there is no
// `process.env` at runtime, so a dynamic var would never reach the SDK.
E2E_TEST_DSN: { static: true },
PUBLIC_E2E_TEST_DSN: { public: true, static: true },
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PUBLIC_E2E_TEST_DSN } from '$app/env/public';
import * as Sentry from '@sentry/sveltekit';

Sentry.init({
traceLifecycle: 'static',
environment: 'qa',
dsn: PUBLIC_E2E_TEST_DSN,
tunnel: 'http://localhost:3031/', // proxy server
tracesSampleRate: 1.0,
});

export const handleError = Sentry.handleErrorWithSentry();
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { E2E_TEST_DSN } from '$app/env/private';
import { handleErrorWithSentry, initCloudflareSentryHandle, sentryHandle } from '@sentry/sveltekit';
import { sequence } from '@sveltejs/kit/hooks';

// not logging anything to console to avoid noise in the test output
export const handleError = handleErrorWithSentry(() => {});

export const handle = sequence(
initCloudflareSentryHandle({
traceLifecycle: 'static',
environment: 'qa', // dynamic sampling bias to keep transactions
dsn: E2E_TEST_DSN,
debug: !!process.env.DEBUG,
tunnel: 'http://localhost:3031/', // proxy server
tracesSampleRate: 1.0,
}),
sentryHandle(),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { PageServerLoad } from './$types';

// Opt the index route out of prerendering so it is served by the Worker and therefore
// exercises `initCloudflareSentryHandle`.
export const prerender = false;

export const load: PageServerLoad = async function load() {
return { message: 'From server load function.' };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>SvelteKit 3 on Cloudflare Workers</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const prerender = false;

export const load = async () => {
throw new Error('Server Load Error on Cloudflare');
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Server Load Error</h1>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const prerender = false;

export const GET = async () => {
throw new Error('Server Route Error on Cloudflare');
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { startEventProxyServer } from '@sentry-internal/test-utils';

startEventProxyServer({
port: 3031,
proxyServerName: 'sveltekit-3-cloudflare-workers',
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { expect, test } from '@playwright/test';
import { waitForError, waitForTransaction } from '@sentry-internal/test-utils';

// These tests guard the Cloudflare execution context lookup in `@sentry/sveltekit`.
//
// `@sveltejs/adapter-cloudflare` 8 renamed `platform.context` to `platform.ctx`. The SDK reads that
// object to get `waitUntil`, and every use of it is optional-chained — so when the lookup misses,
// nothing throws: `flushAndDispose()` is simply never scheduled and the Worker isolate is torn down
// with the events still queued. The assertions below therefore double as flush assertions; if the
// SDK stops finding the execution context, no event reaches the proxy and these time out.

test('sends a server transaction for a Worker-rendered route', async ({ page }) => {
const serverTxnEventPromise = waitForTransaction('sveltekit-3-cloudflare-workers', txnEvent => {
return txnEvent?.transaction === 'GET /';
});

await page.goto('/');

await expect(page.locator('h1')).toHaveText('SvelteKit 3 on Cloudflare Workers');

const serverTxnEvent = await serverTxnEventPromise;

expect(serverTxnEvent).toMatchObject({
transaction: 'GET /',
type: 'transaction',
contexts: {
trace: {
op: 'http.server',
origin: 'auto.http.cloudflare',
},
},
});
});

test('captures a server load error thrown in the Worker', async ({ page }) => {
const errorEventPromise = waitForError('sveltekit-3-cloudflare-workers', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Server Load Error on Cloudflare';
});

await page.goto('/server-load-error');

const errorEvent = await errorEventPromise;

expect(errorEvent.exception?.values?.[0]).toMatchObject({
value: 'Server Load Error on Cloudflare',
mechanism: expect.objectContaining({ handled: false }),
});
});

// `handleError` runs after `wrapRequestHandler` has already returned for streamed responses, so it
// resolves the execution context a second time and flushes through it independently.
test('captures a server route error thrown in the Worker', async ({ request }) => {
const errorEventPromise = waitForError('sveltekit-3-cloudflare-workers', errorEvent => {
return errorEvent?.exception?.values?.[0]?.value === 'Server Route Error on Cloudflare';
});

await request.get('/server-route-error').catch(() => {
// a 500 is expected here
});

const errorEvent = await errorEventPromise;

expect(errorEvent.exception?.values?.[0]?.value).toBe('Server Route Error on Cloudflare');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
// Kit 3 generates the parent config at `node_modules/$app/tsconfig`
// and never writes `.svelte-kit/tsconfig.json`.
"extends": "$app/tsconfig",
"include": ["src"],
"compilerOptions": {
"allowJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"allowImportingTsExtensions": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { sentrySvelteKit } from '@sentry/sveltekit';
import adapter from '@sveltejs/adapter-cloudflare';
import { sveltekit } from '@sveltejs/kit/vite';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { defineConfig } from 'vite';

export default defineConfig({
plugins: [
sentrySvelteKit({
autoUploadSourceMaps: false,
}),
// SvelteKit 3 no longer reads `svelte.config.js` — configuration is passed
// directly to the `sveltekit()` Vite plugin instead.
sveltekit({
preprocess: vitePreprocess(),
adapter: adapter(),
prerender: {
handleHttpError: 'ignore',
},
}),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "sveltekit-3-cloudflare-workers",
"compatibility_date": "2026-08-01",
"compatibility_flags": ["nodejs_compat"],
"main": ".svelte-kit/cloudflare/_worker.js",
"assets": {
"directory": ".svelte-kit/cloudflare",
"binding": "ASSETS",
},
}
Loading