Skip to main content
Frameworks

SvelteKit on Easel

Deploy SvelteKit applications to Easel with SSR, streaming, prerendering, form actions, hooks, and API endpoints.

At a glance

Support levelProduction-ready
RenderingStatic, SSR, streaming, and client-side rendering
Server runtimeNode.js
Required adapter@sveltejs/adapter-vercel
Automatic detectionYes
Most recently tested withSvelteKit 2.70.x

Deploy a SvelteKit application

Install the Vercel adapter:

npm install -D @sveltejs/adapter-vercel

Then configure it in svelte.config.js:

svelte.config.js
import adapter from "@sveltejs/adapter-vercel";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";

/** @type {import("@sveltejs/kit").Config} */
const config = {
  preprocess: vitePreprocess(),

  kit: {
    adapter: adapter(),
  },
};

export default config;

Deploy the project by connecting its Git repository to Easel or using the CLI:

easel deploy

Easel detects SvelteKit, installs the project’s dependencies, runs the production build, and deploys the output generated by the adapter.

The default build command is:

vite build

You can override the install command, build command, root directory, Node.js version, and environment variables in the project settings.

Why run SvelteKit on Easel?

Easel provides a production SvelteKit deployment path using the framework’s standard adapter system.

A SvelteKit deployment includes:

  • Prerendered pages and static assets served through the Easel CDN
  • Regional functions for server-rendered pages and server-side code
  • Streaming responses from server load functions and endpoints
  • Support for form actions, hooks, and API routes
  • Preview deployments for every branch and pull request
  • Built-in logs, metrics, traces, WAF, and attack protection
  • Vercel-compatible build output without requiring a Vercel deployment

You can inspect function execution, request routing, deployment output, and resource usage from the Easel dashboard.

Why is the Vercel adapter required?

SvelteKit uses adapters to convert a framework build into output that a hosting platform can deploy.

@sveltejs/adapter-vercel produces a Build Output API-compatible deployment containing:

  • Client assets
  • Prerendered pages
  • Server-side application code
  • Route and function configuration
  • Redirect and routing metadata

Easel consumes this output directly. Using the adapter does not deploy the application to Vercel or require a Vercel account.

Do not use adapter-auto for production Easel deployments. Automatic adapter selection can change based on the detected build environment and does not provide an explicit, reproducible deployment contract.

What Easel deploys

Easel maps the output generated by the SvelteKit adapter to the appropriate platform resources.

SvelteKit outputEasel resource
Files in staticCDN assets
Client JavaScript and CSSCDN assets
Prerendered pagesCDN assets
Server-rendered pagesEasel Functions
Server load functionsEasel Functions
Form actionsEasel Functions
+server endpointsEasel Functions
Server hooksSvelteKit server function
Redirects and route metadataEasel request routing

Prerendered routes are excluded from the dynamic server manifest where possible, reducing the amount of server code required to handle requests.

Routes that require request-time data remain part of the server application and execute in Easel Functions.

Function topology

By default, the adapter can package the server-rendered portion of the SvelteKit application into a shared function.

This allows layouts, pages, endpoints, hooks, and application state to share a server bundle while SvelteKit continues to route requests internally.

The adapter also supports splitting routes into separate functions. Easel support for adapter-level and route-level function splitting depends on the generated Build Output API configuration.

Use route splitting only when it provides a concrete benefit, such as:

  • Isolating a resource-intensive endpoint
  • Assigning different execution settings to a route
  • Reducing the deployment size of frequently invoked routes
  • Preventing one route’s dependencies from affecting unrelated routes

Splitting every route can increase build output, deployment complexity, and the number of independently initialized function bundles.

Supported features

FeatureSupportNotes
Server-side renderingSupportedDynamic routes execute in Easel Functions
PrerenderingSupportedGenerated files are deployed to the CDN
Client-side renderingSupportedControlled through SvelteKit page options
Streaming server loadsSupportedPromises returned from server load functions can stream as they resolve
Streaming endpointsSupportedStandard ReadableStream responses are forwarded without full-response buffering
Universal load functionsSupportedRun according to SvelteKit’s normal server and browser lifecycle
Server load functionsSupportedExecute in the SvelteKit server function
Form actionsSupportedIncludes progressive enhancement with use:enhance
API endpointsSupported+server handlers execute in Easel Functions
Server hooksSupportedIncludes handle, handleFetch, and handleError
Universal hooksSupportedIncluded in the generated application bundle
Cookies and sessionsSupportedStandard SvelteKit cookie APIs are available
Redirects and errorsSupportedStandard SvelteKit response behavior is preserved
Route parametersSupportedIncludes optional, rest, and matched parameters
Route groupsSupportedGroups remain an application-level routing concern
Shallow routingSupportedClient-side framework behavior
Service workersSupportedGenerated service-worker assets are deployed statically
Private environment variablesSupportedAvailable only to server-side code
Public environment variablesSupportedExposed according to SvelteKit’s public-prefix rules
Dynamic environment variablesSupportedResolved from the function environment at runtime
Static environment variablesSupportedInlined during the production build
Server instrumentationSupportedIncluded when emitted by the adapter
Base pathsSupportedConfigure through kit.paths.base
Trailing-slash behaviorSupportedControlled by SvelteKit route options
WebSocketsNot supportedFunction routes use HTTP request-response semantics
Persistent local filesystemNot supportedFunction filesystems are ephemeral
Scheduled tasksNot provided by SvelteKitUse Easel scheduled functions or an external scheduler

Rendering

SvelteKit lets an application combine static, server-rendered, and client-rendered routes.

Rendering behavior can be configured in:

  • +page.js
  • +page.server.js
  • +layout.js
  • +layout.server.js
  • +server.js

Settings exported from a layout apply to its descendant routes unless overridden.

Server-side rendering

Pages are server-rendered by default.

A server load function can read private environment variables, access a database, inspect cookies, and return data to the page:

src/routes/account/+page.server.ts
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async ({ locals }) => {
  return {
    user: locals.user,
  };
};

The corresponding page receives the data:

src/routes/account/+page.svelte
<script lang="ts">
  import type { PageData } from "./$types";

  let { data }: { data: PageData } = $props();
</script>

<h1>Welcome, {data.user.name}</h1>

The load function and page render execute in an Easel Function. The rendered HTML is returned to the browser and hydrated according to the application’s SvelteKit configuration.

Prerendering

Routes that do not require request-time data can be generated during the build:

src/routes/about/+page.ts
export const prerender = true;

Prerendered pages are deployed as static files and served through the Easel CDN without invoking a function.

You can enable prerendering for a group of routes from a layout:

src/routes/(marketing)/+layout.ts
export const prerender = true;

Child routes can override the inherited value:

src/routes/(marketing)/pricing/+page.server.ts
export const prerender = false;

Use prerender = "auto" when some known route entries should be generated during the build while other parameter values remain available through server rendering:

src/routes/blog/[slug]/+page.server.ts
import type { EntryGenerator } from "./$types";

export const prerender = "auto";

export const entries: EntryGenerator = () => {
  return [
    { slug: "introducing-easel" },
    { slug: "deploying-sveltekit" },
  ];
};

Routes fully removed from the dynamic manifest can reduce the size of the application’s server bundle.

Client-side rendering

Disable server rendering for a route when it must run entirely in the browser:

src/routes/dashboard/+page.ts
export const ssr = false;

This produces a client-rendered application shell for that route. Browser requests for the route still require the appropriate fallback and routing metadata generated by the adapter.

You can also disable hydration for content that should remain static HTML:

src/routes/legal/+page.ts
export const csr = false;

Use these options deliberately. Prefer server rendering when you want better initial HTML, progressive enhancement, and resilience if client-side JavaScript fails to load.

Streaming

SvelteKit server load functions can return promises that resolve after the initial page data.

src/routes/products/[id]/+page.server.ts
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async ({ params, fetch }) => {
  const product = await loadProduct(params.id);

  return {
    product,
    reviews: fetch(`/api/products/${params.id}/reviews`).then((response) =>
      response.json(),
    ),
  };
};

The page can render the required product information immediately and update when the reviews resolve:

src/routes/products/[id]/+page.svelte
<script lang="ts">
  import type { PageData } from "./$types";

  let { data }: { data: PageData } = $props();
</script>

<h1>{data.product.name}</h1>

{#await data.reviews}
  <p>Loading reviews…</p>
{:then reviews}
  <ul>
    {#each reviews as review}
      <li>{review.body}</li>
    {/each}
  </ul>
{:catch}
  <p>Reviews could not be loaded.</p>
{/await}

Easel forwards the response as SvelteKit produces it rather than waiting for every streamed promise to settle.

Once a response has started streaming, its status and headers can no longer be changed. Do not attempt to redirect or call setHeaders from inside a promise that resolves after streaming begins.

Streaming load data requires browser JavaScript. Without JavaScript, the browser waits for the complete server-rendered response.

Form actions

SvelteKit form actions execute in the application’s server function.

src/routes/contact/+page.server.ts
import { fail } from "@sveltejs/kit";
import type { Actions } from "./$types";

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const email = data.get("email");

    if (typeof email !== "string" || !email.includes("@")) {
      return fail(400, {
        email,
        invalid: true,
      });
    }

    await saveContactRequest({
      email,
    });

    return {
      success: true,
    };
  },
} satisfies Actions;

Use a standard HTML form for progressive enhancement:

src/routes/contact/+page.svelte
<script lang="ts">
  import { enhance } from "$app/forms";
  import type { ActionData } from "./$types";

  let { form }: { form: ActionData } = $props();
</script>

<form method="POST" use:enhance>
  <label>
    Email
    <input
      name="email"
      type="email"
      value={form?.email ?? ""}
      required
    />
  </label>

  <button type="submit">Contact me</button>
</form>

{#if form?.invalid}
  <p>Enter a valid email address.</p>
{/if}

{#if form?.success}
  <p>Your request was received.</p>
{/if}

The form remains functional without browser JavaScript. With use:enhance, SvelteKit submits the request without a full-page reload and updates the action data in place.

API endpoints

Create HTTP endpoints with +server files:

src/routes/api/health/+server.ts
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";

export const GET: RequestHandler = () => {
  return json({
    ok: true,
    timestamp: new Date().toISOString(),
  });
};

Endpoint handlers can return any standard Response, including JSON, redirects, files, Server-Sent Events, and streaming bodies.

A streaming endpoint can return a ReadableStream:

src/routes/api/events/+server.ts
import type { RequestHandler } from "./$types";

const encoder = new TextEncoder();

export const GET: RequestHandler = () => {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue(
        encoder.encode('data: {"status":"connected"}\n\n'),
      );

      controller.close();
    },
  });

  return new Response(stream, {
    headers: {
      "content-type": "text/event-stream",
      "cache-control": "no-cache",
    },
  });
};

Long-lived responses remain subject to the route’s function execution limit. Use a persistent service for indefinite connections.

Hooks

Server hooks run inside the SvelteKit server function.

Use handle to authenticate requests, populate event.locals, modify responses, or bypass normal route handling:

src/hooks.server.ts
import type { Handle } from "@sveltejs/kit";

export const handle: Handle = async ({ event, resolve }) => {
  const session = event.cookies.get("session");

  event.locals.user = session
    ? await findUserBySession(session)
    : null;

  const response = await resolve(event);

  response.headers.set("x-application", "easel-sveltekit");

  return response;
};

The populated value is available to server load functions, form actions, and endpoints:

src/routes/account/+page.server.ts
import { redirect } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = ({ locals }) => {
  if (!locals.user) {
    redirect(303, "/login");
  }

  return {
    user: locals.user,
  };
};

Use handleFetch when you need to modify server-side requests made through SvelteKit’s enhanced fetch implementation.

Cookies and sessions

SvelteKit’s cookies API is available in server load functions, actions, endpoints, and hooks:

src/routes/login/+page.server.ts
import { redirect } from "@sveltejs/kit";
import type { Actions } from "./$types";

export const actions = {
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const session = await createSession(data);

    cookies.set("session", session.id, {
      path: "/",
      httpOnly: true,
      sameSite: "lax",
      secure: true,
      maxAge: 60 * 60 * 24 * 30,
    });

    redirect(303, "/account");
  },
} satisfies Actions;

Cookies are sent through Easel’s request pipeline to the application function and returned through standard Set-Cookie response headers.

Store durable session state in a database or shared session store rather than in function memory.

Cache control

Set cache headers from server load functions with setHeaders:

src/routes/blog/[slug]/+page.server.ts
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async ({
  params,
  setHeaders,
}) => {
  const post = await loadPost(params.slug);

  setHeaders({
    "cache-control":
      "public, max-age=0, s-maxage=300, stale-while-revalidate=86400",
  });

  return {
    post,
  };
};

For endpoint responses, set headers directly:

src/routes/api/catalog/+server.ts
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";

export const GET: RequestHandler = async () => {
  const products = await loadProducts();

  return json(products, {
    headers: {
      "cache-control":
        "public, max-age=0, s-maxage=300, stale-while-revalidate=3600",
    },
  });
};

Easel’s CDN interprets supported HTTP cache directives for eligible responses. See Cache responses at the edge and Invalidate and revalidate cached content.

Do not publicly cache responses containing user-specific data, authentication state, private cookies, or personalized content.

Environment variables

SvelteKit distinguishes private from public variables and static from dynamic variables.

Private variables

Private variables are available only to server-side code:

src/lib/server/database.ts
import { DATABASE_URL } from "$env/static/private";

export const database = connect(DATABASE_URL);

SvelteKit prevents private environment modules from being imported into browser code.

Public variables

Public variables use the configured public prefix, which is PUBLIC_ by default:

import { PUBLIC_API_ORIGIN } from "$env/static/public";

Public values can be included in browser bundles and must not contain secrets.

Static variables

Static variables are replaced during the build:

import { FEATURE_FLAG } from "$env/static/private";

Changing a static variable requires a new deployment.

Dynamic variables

Dynamic variables are read from the function environment when the application handles a request:

import { env } from "$env/dynamic/private";

export function loadDatabaseConfig() {
  return {
    url: env.DATABASE_URL,
  };
}

Dynamic access is useful when a value should remain outside the generated server bundle or can vary without being statically imported.

Explicit environment variables

Recent SvelteKit 2 releases allow applications to opt into the explicit environment-variable system planned for SvelteKit 3.

When enabled, variables are declared in src/env.ts and imported through $app/env/private or $app/env/public.

Easel supplies the underlying build-time and runtime environment variables. The SvelteKit version and application configuration determine which framework modules expose them.

Adapter configuration

The Vercel adapter accepts options designed for Vercel’s infrastructure. Not every provider-specific option changes an Easel deployment.

The adapter’s primary purpose on Easel is to generate:

  • Server function output
  • Static and prerendered assets
  • Routing configuration
  • Build Output API metadata

Easel project settings remain authoritative for platform resources such as:

  • Function memory
  • CPU allocation
  • Maximum execution duration (framework maxDuration when emitted)
  • Environment variables
  • Scaling limits

Do not rely on adapter memory, duration, or region settings unless the corresponding option is explicitly documented as supported by Easel. Functions run in US East.

Image optimization

Static images imported or referenced by the application deploy normally as client assets.

SvelteKit itself does not prescribe a single runtime image-transformation service. Image behavior depends on the component, preprocessor, or image package used by the application.

Build-time image processing works when it produces ordinary static assets during vite build.

For runtime transformation, use Easel’s image service or another documented image provider. Verify framework-specific image plugins in a preview deployment before relying on them in production.

Base paths

Configure an application mounted below the domain root through kit.paths.base:

svelte.config.js
import adapter from "@sveltejs/adapter-vercel";

export default {
  kit: {
    adapter: adapter(),

    paths: {
      base: "/app",
    },
  },
};

Use SvelteKit’s path helpers when generating links and asset URLs so the configured base path is preserved.

Trailing slashes

Configure trailing-slash behavior from a layout or page:

src/routes/+layout.ts
export const trailingSlash = "always";

Supported values are:

  • "never"
  • "always"
  • "ignore"

The setting also affects paths generated during prerendering.

Service workers

SvelteKit service workers are built as static application assets and deployed through the CDN.

Create one at src/service-worker.js or src/service-worker.ts:

src/service-worker.ts
import { build, files, version } from "$service-worker";

const cacheName = `cache-${version}`;
const assets = [...build, ...files];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(cacheName).then((cache) => cache.addAll(assets)),
  );
});

A service worker executes in the visitor’s browser, not inside an Easel Function. Ensure caching rules account for immutable deployment assets and application-version changes.

Project configuration

Easel detects conventional SvelteKit project settings automatically:

SettingDefault
Install commandDetected from the package manager
Build commandPackage script or vite build
Output directoryRead from the adapter output
Development commandPackage script or vite dev
Node.js versionProject or platform default

For monorepos, set the project root to the directory containing the SvelteKit application’s package.json and svelte.config.js.

The project must include the production adapter in its dependencies and configuration.

Local development

Continue using SvelteKit’s normal development server:

npm run dev

The Vite development server provides SvelteKit routing, server rendering, load functions, actions, hooks, and endpoints locally.

Use an Easel preview deployment to validate behavior that depends on the production platform:

  • Production adapter output
  • Function packaging
  • Streaming through the request pipeline
  • CDN caching
  • Runtime environment variables
  • Function duration and resource limits
  • WAF and security rules
  • Platform resource limits

Every preview deployment uses the production build path and receives its own immutable URL.

Known limitations

WebSockets

SvelteKit routes deployed to Easel Functions cannot accept long-lived WebSocket connections.

Use an external WebSocket provider or a dedicated persistent service for bidirectional communication.

HTTP streaming and Server-Sent Events are separate from WebSockets, but remain subject to function execution limits.

Persistent local filesystem

The function filesystem is ephemeral.

Files written during an invocation are not guaranteed to exist in a later request or on another function instance. Use object storage, a database, or another durable service for persistent data.

Durable background work

Do not start untracked asynchronous work after returning a response.

Function execution may end once the request lifecycle is complete. Use a queue, scheduled function, or durable background-work system for tasks that require retries or must survive function termination.

Adapter-specific infrastructure settings

The Vercel adapter contains options tied specifically to Vercel’s function products.

Easel consumes the portable deployment output, but does not necessarily reproduce every Vercel-specific control. Configure Easel infrastructure from the Easel project settings unless a particular adapter option is documented as supported.

Runtime image plugins

Build-time image output works as static assets. Runtime image packages may depend on provider-specific APIs and should be tested independently.

Troubleshooting

Easel does not detect the server application

Confirm that @sveltejs/adapter-vercel is installed and selected in svelte.config.js.

Do not rely on adapter-auto for the production deployment.

The deployment contains only static files

Check whether prerender = true is exported from the root layout or inherited by the affected routes.

Routes that are fully prerendered do not remain in the dynamic server manifest.

A route fails during prerendering

A prerendered route cannot depend on request-specific cookies, headers, authentication state, or runtime-only data.

Set prerender = false for the route or move the request-dependent work to a dynamically rendered route.

A dynamic route is missing from the prerendered output

SvelteKit must know which parameter values to generate.

Export an entries function or make the route discoverable through links crawled from another prerendered page.

An environment variable is undefined

Confirm that the variable exists in the current Easel environment.

Also verify that the application uses the correct SvelteKit module:

  • Static or dynamic
  • Private or public
  • Legacy $env modules or the explicit $app/env system

Static variables require a new deployment after their values change.

A public variable is unavailable in browser code

By default, public environment variables must use the PUBLIC_ prefix.

Variables without the public prefix remain server-only.

A streamed value never appears early

Ensure the promise is returned from a server load function rather than awaited before the function returns.

Also confirm that the surrounding application code and any upstream services do not buffer the complete response.

Headers fail inside streamed data

Headers and status codes cannot change after the response starts.

Set headers before returning the initial load result, and do not redirect from inside a streamed promise.

Cookies are not being set

Confirm that the cookie includes an appropriate path, and verify the secure and sameSite settings for the deployment environment.

Code works locally but fails after deployment

Inspect the production build logs and function logs from a preview deployment.

Common causes include:

  • Missing runtime environment variables
  • Native packages built for the wrong environment
  • Case-sensitive import paths
  • Provider-specific adapter options
  • Access to persistent local files
  • Code that uses browser APIs during server rendering

Compatibility policy

Easel tests its SvelteKit integration against representative applications covering:

  • Server-side rendering
  • Prerendering
  • Server and universal load functions
  • Streaming promises
  • Form actions
  • Hooks
  • API endpoints
  • Cookies and sessions
  • Static and dynamic environment variables
  • Base paths
  • Redirects
  • Error handling

Stable SvelteKit releases may work beyond the version listed at the top of this page, but the listed version is the most recently verified baseline.

For a newly released SvelteKit version, an experimental framework feature, or a provider-specific adapter option, create a preview deployment before upgrading the production application.