Skip to main content
Frameworks

TanStack Start on Easel

Deploy full-stack TanStack Start applications to Easel with SSR, streaming, server functions, and Nitro.

At a glance

Support levelProduction-ready
RenderingStatic, SSR, streaming, selective SSR, and SPA mode
Server runtimeNode.js
Deployment integrationNitro
Adapter requiredYes
Automatic detectionYes
Most recently tested withTanStack Start RC

Deploy a TanStack Start application

TanStack Start applications use Nitro to produce the server entry and static assets Easel deploys.

Install Nitro:

npm install nitro

Add the Nitro Vite plugin after tanstackStart() and before the React plugin:

vite.config.ts
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";

export default defineConfig({
  plugins: [
    tanstackStart(),
    nitro(),
    react(),
  ],
});

Do not manually configure a Nitro deployment preset for Easel. During the platform build, Easel selects the hosting output it expects.

Connect the project’s Git repository to Easel or deploy it with the CLI:

easel deploy

Easel detects TanStack Start, installs dependencies, runs the production build, and deploys the generated client and server output.

The default build command is:

vite build

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

Why run TanStack Start on Easel?

TanStack Start keeps routing, data loading, and server boundaries inside the application while allowing the deployment target to remain portable.

Easel preserves that model. Your application uses standard TanStack Start and Nitro integrations rather than an Easel-specific framework fork.

A TanStack Start deployment includes:

  • Immutable client assets served through the Easel CDN
  • Regional functions for server rendering and server-side code
  • Streaming responses from routes and server functions
  • Type-safe server functions callable from loaders and components
  • Server routes for external HTTP endpoints
  • Middleware for authentication, context, logging, and request policy
  • Preview deployments for every branch and pull request
  • Built-in logs, metrics, traces, WAF, and attack protection

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

What Easel deploys

The Nitro build produces a client application and a server entry that implements TanStack Start’s request handling.

Easel maps that output to platform resources:

Application outputEasel resource
JavaScript, CSS, fonts, and public filesCDN assets
Prerendered pagesCDN assets
Server-rendered routesEasel Functions
Route loaders requiring server executionEasel Functions
Server functionsApplication server function
Server routesFunction-backed HTTP routes
Start middlewareApplication server function
Streaming responsesFunction response stream
Application telemetryEasel observability pipeline

Static files are deployed immutably to the CDN. Requests requiring TanStack Start’s server runtime are forwarded to the generated server entry.

Server functions are part of the application’s server bundle. They are not deployed as individually configurable Easel Functions unless the build output explicitly separates them.

Supported features

FeatureSupportNotes
File-based routingSupportedRoutes generated by TanStack Router are included in the application build
Full-document SSRSupportedRoutes render in Easel Functions
Streaming SSRSupportedHTML is forwarded as the framework produces it
Client-side navigationSupportedClient assets and route manifests are served through the CDN
Route loadersSupportedLoaders run in the environment selected by the route
beforeLoadSupportedRuns as part of TanStack Router’s route lifecycle
Server functionsSupportedIncludes validated, type-safe calls from client and server code
Streaming server functionsSupportedIncludes ReadableStream and async-generator responses
Server routesSupportedExpose external HTTP endpoints from the Start application
MiddlewareSupportedIncludes request, function, and server-route middleware
Selective SSRSupportedRoutes can opt into full, data-only, or client-only rendering
SPA modeSupportedThe application server provides the required document fallback
Static prerenderingSupported with setupRoutes configured for prerendering are emitted as static output
Incremental Static RegenerationExperimentalDepends on the framework and Nitro output used by the application
Redirects and headersSupportedReturn them through standard framework response APIs
Cookies and sessionsSupportedStandard request and response cookie behavior is preserved
Environment variablesSupportedServer variables remain outside the client bundle unless explicitly exposed
Request cancellationSupportedLong-running server work can observe the request’s AbortSignal
Early HintsRuntime-dependentVerify behavior in a preview deployment
OpenTelemetrySupportedStandard Node.js instrumentation can export through Easel
WebSocketsNot supportedFunction requests use HTTP request-response semantics
Durable background jobsNot supportedUse a dedicated queue or background-work service

TanStack Start and nitro/vite are evolving rapidly. Experimental framework behavior should be validated in a preview deployment before it is promoted to production.

Rendering

TanStack Start allows each route to select how much work happens on the server.

Full SSR

Full SSR sends both the route’s data and rendered markup from the server.

src/routes/products.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/products")({
  loader: async () => {
    return getProducts();
  },
  component: ProductsPage,
});

function ProductsPage() {
  const products = Route.useLoaderData();

  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}

The initial request runs through the TanStack Start server entry in an Easel Function. The generated HTML can begin streaming while the route completes its work.

The browser hydrates the returned document and handles later navigations through TanStack Router.

Data-only SSR

A route can run its loader on the server without rendering its component markup there:

export const Route = createFileRoute("/dashboard")({
  ssr: "data-only",
  loader: async () => {
    return getDashboardData();
  },
  component: DashboardPage,
});

The server returns the route data required by the client, while the component itself renders in the browser.

Client-only rendering

Disable server rendering for a route when it depends entirely on browser APIs or should only render after hydration:

export const Route = createFileRoute("/editor")({
  ssr: false,
  component: EditorPage,
});

The route remains part of the TanStack Start application, but its component does not render during the server response.

Streaming

Easel forwards response chunks as TanStack Start produces them rather than waiting for the entire document to finish rendering.

This allows the initial document shell and completed route regions to reach the browser while slower data is still resolving.

Streaming remains subject to the function’s maximum execution duration. A disconnected client can also cancel request-bound work when the application observes the request’s abort signal.

Server functions

Server functions define server-only logic that can be imported and called from loaders, components, hooks, and other server functions.

src/data/products.functions.ts
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";

const ProductInput = z.object({
  id: z.string(),
});

export const getProduct = createServerFn({
  method: "GET",
})
  .inputValidator(ProductInput)
  .handler(async ({ data }) => {
    return db.product.findUnique({
      where: {
        id: data.id,
      },
    });
  });

Call the function from application code:

const product = await getProduct({
  data: {
    id: "product_123",
  },
});

TanStack Start converts client-side calls into HTTP requests and dispatches them to the corresponding server implementation.

The server handler can access:

  • Databases and private services
  • Server-only environment variables
  • Request headers and cookies
  • Authentication context
  • The request’s cancellation signal
  • Node.js runtime APIs supported by Easel Functions

Server function identifiers and dispatch behavior are generated by TanStack Start during the production build.

Keep server-only code isolated

Use .server.ts files for implementation details that must never enter a client bundle:

src/data/products.server.ts
import { db } from "~/db";

export async function findProduct(id: string) {
  return db.product.findUnique({
    where: {
      id,
    },
  });
}

Import the server-only helper inside the server function handler:

src/data/products.functions.ts
import { createServerFn } from "@tanstack/react-start";

export const getProduct = createServerFn({
  method: "GET",
})
  .inputValidator((id: string) => id)
  .handler(async ({ data: id }) => {
    const { findProduct } = await import("./products.server");

    return findProduct(id);
  });

TanStack Start’s build-time environment separation prevents server implementation code from being included in the browser bundle.

Streaming from server functions

Server functions can return streaming data using a ReadableStream or async generator.

import { createServerFn } from "@tanstack/react-start";

export const generateReport = createServerFn({
  method: "POST",
}).handler(async function* () {
  yield {
    status: "starting",
  };

  for await (const section of buildReport()) {
    yield {
      status: "progress",
      section,
    };
  }

  yield {
    status: "complete",
  };
});

Easel keeps the HTTP response open and forwards chunks as the server function yields them.

The invocation remains active and billable until the stream closes, the client disconnects, or the function reaches its execution limit.

For durable work that must continue after a disconnect or survive function termination, submit the job to a queue and stream or poll its persisted state separately.

Server routes

Use server routes for endpoints intended to be called outside the TanStack Start application.

src/routes/api/health.ts
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/api/health")({
  server: {
    handlers: {
      GET: async () => {
        return Response.json({
          ok: true,
          checkedAt: new Date().toISOString(),
        });
      },
    },
  },
});

Server routes can return:

  • JSON responses
  • HTML
  • Redirects
  • Custom headers and status codes
  • ReadableStream responses
  • Server-Sent Events

Server routes and server functions are both handled by the generated Start server entry, but they serve different purposes:

PrimitiveIntended use
Server functionType-safe calls made by your TanStack Start application
Server routePublic or externally called HTTP endpoint

Use a server route for webhooks, public APIs, health checks, file responses, and integrations that do not call through the generated Start client.

Middleware

TanStack Start middleware can add shared request behavior without duplicating it across routes or server functions.

Common uses include:

  • Authentication and authorization
  • Request context
  • Logging
  • Rate-limit metadata
  • Security policy
  • Response headers
  • Error handling
import {
  createMiddleware,
  getRequestHeader,
} from "@tanstack/react-start";

export const authMiddleware = createMiddleware().server(
  async ({ next }) => {
    const authorization = getRequestHeader("authorization");
    const user = await authenticate(authorization);

    if (!user) {
      throw new Response("Unauthorized", {
        status: 401,
      });
    }

    return next({
      context: {
        user,
      },
    });
  },
);

This middleware runs inside the TanStack Start application function. It should not be confused with Easel’s CDN or WAF request pipeline.

Use Easel security rules for infrastructure-level filtering and TanStack Start middleware for application-level behavior.

Static prerendering

TanStack Start can prerender selected routes during the production build.

Prerendered HTML and its associated client assets are deployed to the Easel CDN. Requests for those pages do not need to invoke the application function unless the route later requires dynamic behavior.

Use prerendering for content that can be generated without request-specific data, such as:

  • Marketing pages
  • Documentation
  • Blog posts
  • Public product pages
  • Terms and policy pages

The precise configuration depends on the TanStack Start version used by the project. Easel deploys the static output generated by Start and Nitro rather than discovering routes independently.

When adding or changing prerender configuration, verify the generated routes in a preview deployment.

Caching

TanStack Start does not impose one universal application-cache model. Caching behavior can come from:

  • Browser cache headers
  • Easel’s CDN
  • Application-level caches
  • Nitro caching utilities
  • Database or external cache services
  • Prerendered output
  • Experimental ISR support

CDN caching

Set standard cache headers on server-route responses:

export const Route = createFileRoute("/api/catalog")({
  server: {
    handlers: {
      GET: async () => {
        const catalog = await getCatalog();

        return Response.json(catalog, {
          headers: {
            "Cache-Control":
              "public, s-maxage=300, stale-while-revalidate=60",
          },
        });
      },
    },
  },
});

Shared-cache directives allow eligible responses to be cached by Easel’s CDN.

Do not publicly cache responses containing user-specific or authenticated data.

Server function caching

Server functions may set response headers through TanStack Start’s server request utilities.

import {
  createServerFn,
  setResponseHeaders,
} from "@tanstack/react-start";

export const getPublicCatalog = createServerFn({
  method: "GET",
}).handler(async () => {
  setResponseHeaders(
    new Headers({
      "Cache-Control":
        "public, s-maxage=300, stale-while-revalidate=60",
    }),
  );

  return getCatalog();
});

Server functions are frequently user-specific. Cache them publicly only when their output is safe to share across callers.

Nitro storage and caching

Nitro provides storage and caching abstractions, but their durability depends on the configured storage driver.

Do not rely on in-memory or local-filesystem storage to persist across Easel Function instances. Configure an external durable driver when cached data must be shared between instances or survive instance replacement.

Environment variables

Configure environment variables in the Easel dashboard or CLI.

Server-only variables can be accessed in server functions, server routes, middleware, and route loaders that execute on the server:

const databaseUrl = process.env.DATABASE_URL;

Only expose a value to browser code when it is intentionally public. Vite statically replaces client-visible variables during the build:

const apiOrigin = import.meta.env.VITE_PUBLIC_API_ORIGIN;

Any value available through import.meta.env in client code should be treated as public.

Changing a build-time client variable requires a new deployment. Use separate values for production, preview, and development environments when necessary.

OpenTelemetry

TanStack Start applications running in Easel Functions can use standard Node.js OpenTelemetry instrumentation.

Instrument:

  • Server-rendered requests
  • Route loaders
  • Server functions
  • Server routes
  • Database queries
  • External API requests
  • Application-defined operations

Easel can collect application spans alongside function metrics, request logs, and platform traces.

See OpenTelemetry for how Easel collects application spans into request Traces.

Project configuration

Easel detects conventional TanStack Start settings automatically:

SettingDefault
Install commandDetected from the package manager
Build commandPackage script or vite build
Development commandPackage script or vite dev
Client outputDetected from the Nitro build
Server outputDetected from the Nitro build
Node.js versionProject or platform default

For monorepos, set the project root to the directory containing the TanStack Start application’s package.json and vite.config.

Do not manually set a different Nitro preset or NITRO_PRESET for the Easel production build. Overriding the preset may produce a server shape that Easel cannot deploy.

Local development

Continue using TanStack Start’s normal Vite development server:

npm run dev

The Nitro plugin integrates the application server into the Vite development environment.

Local development is best for application logic, routes, loaders, server functions, and middleware. Use an Easel preview deployment to validate platform-specific behavior, including:

  • Production bundling
  • Function execution
  • CDN caching
  • Streaming through the production request path
  • Environment variables
  • Function duration and resource limits
  • WAF and security rules
  • Production observability

A preview deployment follows the same deployment path as production and receives its own immutable URL.

Known limitations

Nitro integration maturity

The nitro/vite integration uses Vite’s environment APIs and remains under active development.

TanStack Start, Nitro, or Vite updates can change the generated server output. Review framework release notes and create a preview deployment before upgrading production dependencies.

Pin framework and Nitro versions when deployment stability is more important than receiving updates immediately.

WebSockets

TanStack Start applications deployed to Easel Functions cannot accept long-lived WebSocket connections.

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

Streaming HTTP responses and Server-Sent Events are separate from WebSockets and can operate within the function’s execution limits.

Local filesystem

Function filesystems are ephemeral. Files written during one invocation are not guaranteed to exist in later invocations or on another instance.

Use object storage, a database, or another durable service for persistent data.

In-memory state

Global variables and in-memory caches may survive across requests handled by the same warm instance, but they are not shared across instances and can disappear at any time.

Do not use process memory as the source of truth for sessions, locks, queues, rate limits, or application data.

Background work

Work started during a request is not guaranteed to continue after the function invocation ends.

Use a durable queue or background-work service for jobs that require retries, exceed request duration limits, or must survive process termination.

Incremental Static Regeneration

TanStack Start and Nitro’s incremental regeneration capabilities are evolving. Treat ISR behavior as experimental unless the specific route configuration has been verified against the framework and Nitro versions used by the project.

For predictable static output, use build-time prerendering. For dynamic responses, use explicit cache headers or an application cache.

Troubleshooting

The build does not produce a server entry

Confirm that Nitro is installed:

npm install nitro

Then confirm that nitro() appears in vite.config.ts after tanstackStart():

plugins: [
  tanstackStart(),
  nitro(),
  react(),
]

A standard TanStack Start client build without a compatible server output does not contain the production entry Easel expects.

Easel cannot deploy the generated output

Remove any manually configured Nitro preset or NITRO_PRESET environment variable.

Easel selects its supported hosting output during the platform build. A manually selected Node, Bun, or provider-specific preset may produce a different directory structure or server interface.

A server function is unavailable

Confirm that the function is created with createServerFn and exported from a module included in the production build.

Keep server-only implementation details in .server.ts modules and import them from inside the server handler.

Server-only code appears in the client build

Move private implementation code into a .server.ts file. Do not export secrets or database clients from modules that can run in the browser.

Also verify that environment-variable names intended for the server are not exposed through Vite’s public client-variable prefix.

A route works locally but fails after deployment

Create a preview deployment and inspect the build and function logs.

Common causes include:

  • Missing production environment variables
  • Case-sensitive file paths
  • Native dependencies unavailable for the selected runtime
  • Reliance on the local filesystem
  • A manually selected Nitro preset
  • Code that assumes one long-lived server process
  • Browser-only APIs executing during SSR

A stream is interrupted

Confirm that the application closes the stream normally and handles client cancellation.

Long-running streams remain subject to the function’s maximum duration. Use a durable job model when the underlying operation may outlive the request.

Cached data is shared between users

Do not mark authenticated or personalized responses as public.

Use private or no-store for user-specific responses, and include appropriate Vary headers where cache behavior depends on request headers.

Compatibility policy

Easel tests its TanStack Start integration against representative applications covering:

  • Full-document SSR
  • Streaming
  • Route loaders
  • Server functions
  • Streaming server functions
  • Server routes
  • Middleware
  • Selective SSR
  • SPA mode
  • Static assets
  • Static prerendering
  • Environment variables
  • Production Nitro output

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

Because TanStack Start and nitro/vite are under active development, deploy dependency upgrades to a preview environment before promoting them to production.