Skip to main content
Frameworks

Nuxt on Easel

Deploy Nuxt applications to Easel with SSR, streaming, API routes, hybrid rendering, and Nitro-powered caching.

At a glance

Support levelProduction-ready
RenderingStatic, client-rendered, SSR, streaming, and hybrid
Server runtimeNode.js
Server engineNitro
Adapter requiredNo
Automatic detectionYes
Most recently tested withNuxt 3.15.x

Deploy a Nuxt application

Easel detects Nuxt projects automatically. An existing application can be deployed without changing its Nuxt configuration.

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

easel deploy

Easel detects the package manager, installs dependencies, runs the Nuxt production build, and provisions the infrastructure required by the generated Nitro output.

The default build command is:

nuxt build

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

Why run Nuxt on Easel?

Easel deploys Nuxt’s portable Nitro output while keeping the resulting infrastructure visible and configurable.

A Nuxt deployment includes:

  • Immutable public assets served through the Easel CDN
  • Regional functions for server-rendered pages and Nitro handlers
  • Support for Nuxt server routes and server middleware
  • Hybrid rendering through Nitro route rules
  • Distributed caching for cached server responses
  • Preview deployments for every branch and pull request
  • Built-in logs, metrics, traces, WAF, and attack protection

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

What Easel deploys

Nuxt uses Nitro as its server engine. During the production build, Nitro converts your application into a portable server entry, route metadata, prerendered pages, and public assets.

Easel maps that output to the corresponding platform resources:

Nuxt outputEasel resource
Files in public/CDN assets
Nuxt client bundlesCDN assets
Prerendered pagesCDN assets
Server-rendered pagesEasel Functions
Routes in server/api/Easel Functions
Routes in server/routes/Easel Functions
Server middlewareApplication function
Nitro pluginsApplication function
Cached Nitro responsesRuntime Cache and CDN
Redirects and headersRequest-routing configuration

Static files are deployed immutably. Dynamic rendering and server handlers run through the generated Nitro server entry.

Nitro deployment preset

Do not manually select a Nitro deployment preset for an Easel production build.

Easel selects the compatible hosting output when it runs the Nuxt build. Setting nitro.preset, NITRO_PRESET, or SERVER_PRESET yourself may produce output intended for another platform or runtime.

A standard Easel configuration does not require a Nitro preset:

nuxt.config.ts
export default defineNuxtConfig({
  // No Nitro preset is required.
});

You can continue using other Nitro configuration options, including route rules, plugins, prerender settings, storage configuration, and server assets.

Supported features

FeatureSupportNotes
Nuxt pages and layoutsSupportedIncludes file-based routing, nested layouts, and error pages
Server-side renderingSupportedDynamic pages run in Easel Functions
Client-side renderingSupportedIncludes applications configured with ssr: false
Static generationSupportedGenerated output is deployed to the CDN
Hybrid renderingSupportedControlled through Nitro route rules
Streaming responsesSupportedNitro and h3 response streams are forwarded incrementally
Server API routesSupportedIncludes handlers in server/api/
Server routesSupportedIncludes handlers in server/routes/
Server middlewareSupportedRuns inside the application function
Nitro pluginsSupportedLoaded when the generated server entry starts
PrerenderingSupportedIncludes build-time route crawling and explicit prerender routes
Route redirectsSupportedIncludes redirects configured through route rules
Route headersSupportedIncludes headers configured through route rules
Route cachingSupportedCached responses use Easel’s distributed Runtime Cache
SWR route rulesSupportedStale content may be served while regeneration runs
ISR route rulesSupportedCached route output can be regenerated after expiration
Runtime configurationSupportedServer and public runtime configuration are available
Cookies and sessionsSupportedStandard Nitro and h3 cookie APIs are available
Server-Sent EventsSupportedSubject to function duration and connection limits
@nuxt/imagePartialStatic assets work normally; dynamic provider compatibility depends on the selected image provider
Nitro storagePartialEphemeral and supported external drivers work; local persistent storage does not
Scheduled tasksNot supportedUse an external scheduler to invoke an HTTP route
WebSocketsNot supportedEasel Functions do not accept long-lived WebSocket upgrades
Persistent local filesystemNot supportedFunction filesystems are ephemeral

Experimental Nuxt and Nitro capabilities can change their deployment contract. Validate experimental features in a preview deployment before promoting them to production.

Rendering modes

Nuxt supports several rendering strategies within the same application. Easel deploys each route according to the output generated by Nuxt and Nitro.

Server-side rendering

Nuxt renders pages on the server by default:

pages/products.vue
<script setup lang="ts">
const { data: products } = await useFetch("/api/products");
</script>

<template>
  <main>
    <h1>Products</h1>

    <ul>
      <li v-for="product in products" :key="product.id">
        {{ product.name }}
      </li>
    </ul>
  </main>
</template>

Routes that require request-time execution run through the generated Nitro server entry in an Easel Function.

Response streams are forwarded as Nitro produces them rather than waiting for the entire page to finish rendering.

Client-side rendering

To create a client-rendered application, disable server rendering:

nuxt.config.ts
export default defineNuxtConfig({
  ssr: false,
});

The generated HTML, JavaScript, CSS, and other assets are deployed to the CDN. Easel configures the deployment so application routes can fall back to the generated entry page where required.

Server routes can still be deployed separately when they are included in the Nitro build.

Static generation

Use Nuxt’s generation command for a static deployment:

nuxt generate

Nuxt prerenders the application and writes the static output under .output/public. Easel deploys this output directly to the CDN.

A static deployment does not include request-time server rendering. Any Nitro server routes required by the application must be deployed through the standard nuxt build path instead.

Hybrid rendering

Use route rules to choose rendering and caching behavior for individual paths:

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/": {
      prerender: true,
    },

    "/blog/**": {
      swr: 3600,
    },

    "/dashboard/**": {
      ssr: true,
    },

    "/admin/**": {
      ssr: false,
    },
  },
});

This allows one Nuxt application to combine static pages, cached dynamic pages, request-time rendering, and client-rendered sections.

Route rules

Easel supports the principal Nitro and Nuxt route rules used for rendering, caching, redirects, and response headers.

Prerender a route

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/about": {
      prerender: true,
    },
  },
});

The page is rendered during the build and deployed as a static CDN asset.

Disable server rendering

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/app/**": {
      ssr: false,
    },
  },
});

The route is rendered by the browser rather than by the Nuxt server.

Add response headers

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/assets/**": {
      headers: {
        "cache-control": "public, max-age=31536000, immutable",
      },
    },
  },
});

Redirect a route

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/old-page": {
      redirect: {
        to: "/new-page",
        statusCode: 308,
      },
    },
  },
});

Cache a server route

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/api/products": {
      cache: {
        maxAge: 300,
      },
    },
  },
});

Easel stores eligible cached responses in its distributed Runtime Cache rather than relying on the local filesystem of one function instance.

Serve stale content while revalidating

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/blog/**": {
      swr: 3600,
    },
  },
});

Easel can return a cached response immediately after it becomes stale while Nitro regenerates the route in the background.

The invocation performing regeneration remains subject to the route’s function duration limit.

Incremental regeneration

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/products/**": {
      isr: 600,
    },
  },
});

The generated route output can be cached and regenerated after the configured interval.

Use preview deployments to verify caching behavior when combining multiple route rules or using experimental Nitro options.

Server routes

Create an API endpoint under server/api:

server/api/products.get.ts
export default defineEventHandler(async () => {
  return {
    products: [
      {
        id: "starter",
        name: "Starter",
      },
      {
        id: "pro",
        name: "Pro",
      },
    ],
  };
});

The route is available at:

/api/products

Nuxt server routes are bundled into the generated Nitro server application and run in Easel Functions.

Dynamic routes

Use a filename parameter for dynamic routes:

server/api/products/[id].get.ts
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, "id");

  return {
    id,
  };
});

Request bodies

Read a JSON body with Nitro’s standard h3 utilities:

server/api/products.post.ts
export default defineEventHandler(async (event) => {
  const product = await readBody(event);

  return {
    created: true,
    product,
  };
});

Response headers

server/api/products.get.ts
export default defineEventHandler((event) => {
  setResponseHeader(event, "cache-control", "public, max-age=60");

  return {
    generatedAt: new Date().toISOString(),
  };
});

Server middleware

Place server middleware in server/middleware:

server/middleware/request-id.ts
export default defineEventHandler((event) => {
  const requestId =
    getRequestHeader(event, "x-request-id") ?? crypto.randomUUID();

  setResponseHeader(event, "x-request-id", requestId);
  event.context.requestId = requestId;
});

Server middleware runs as part of the Nitro application function before the matching server route or page render.

Unlike CDN routing rules or Easel WAF rules, Nuxt server middleware requires application-function execution.

Use Easel’s request-routing and security configuration for logic that should run before the application function is invoked.

Nitro plugins

Nitro plugins run when a new application function instance initializes:

server/plugins/instrumentation.ts
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook("request", (event) => {
    event.context.startedAt = performance.now();
  });

  nitroApp.hooks.hook("afterResponse", (event) => {
    const startedAt = event.context.startedAt;

    if (typeof startedAt === "number") {
      console.log({
        path: event.path,
        duration: performance.now() - startedAt,
      });
    }
  });
});

Initialization can run again whenever Easel starts a new function instance. Do not depend on a plugin running exactly once across the deployment.

Connections and in-memory state created by a plugin belong to the individual function instance and are not shared globally.

Runtime configuration

Define private and public configuration in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    databaseUrl: "",
    apiSecret: "",

    public: {
      apiOrigin: "",
    },
  },
});

Set the production values using environment variables:

NUXT_DATABASE_URL
NUXT_API_SECRET
NUXT_PUBLIC_API_ORIGIN

Private runtime configuration is available only to server-side code:

server/api/config.get.ts
export default defineEventHandler((event) => {
  const config = useRuntimeConfig(event);

  return {
    configured: Boolean(config.databaseUrl),
  };
});

Public runtime configuration is also exposed to browser code:

pages/index.vue
<script setup lang="ts">
const config = useRuntimeConfig();
</script>

<template>
  <p>API: {{ config.public.apiOrigin }}</p>
</template>

Configure values separately for development, preview, and production environments in Easel.

Do not place secrets under runtimeConfig.public.

Prerendering

Configure Nitro’s prerenderer in nuxt.config.ts:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ["/", "/about", "/pricing"],

      crawlLinks: true,
    },
  },
});

Prerendered routes are produced during the build and deployed as CDN assets.

Routes discovered through link crawling must be reachable from another prerendered page. Add routes explicitly when they cannot be discovered during the build.

You can also add routes programmatically:

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    async "nitro:config"(nitroConfig) {
      const posts = await fetchPostSlugs();

      nitroConfig.prerender?.routes?.push(
        ...posts.map((slug) => `/blog/${slug}`),
      );
    },
  },
});

Build-time data sources must be reachable from Easel’s build environment.

Caching

Nuxt and Nitro support multiple caching mechanisms. Easel provides distributed storage for supported server-response caches so cached values are not restricted to one function instance.

Cached event handlers

server/api/products.get.ts
export default defineCachedEventHandler(
  async () => {
    return fetchProducts();
  },
  {
    maxAge: 300,
    name: "products",
  },
);

Cached functions

const getProducts = defineCachedFunction(
  async () => {
    return fetchProductsFromDatabase();
  },
  {
    maxAge: 300,
    name: "products",
  },
);

Route-rule caching

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    "/api/catalog": {
      cache: {
        maxAge: 300,
      },
    },
  },
});

Cache entries can be shared between active function instances when the generated Nitro integration uses Easel’s Runtime Cache driver.

Do not use the function’s local filesystem as a persistent cache.

Nitro storage

Nitro exposes a storage abstraction that can use memory, the filesystem, Redis, object storage, and other drivers.

In-memory storage is scoped to one active function instance:

const storage = useStorage("cache");

Values stored only in memory may disappear when an instance is stopped and are not automatically visible to other instances.

Local filesystem storage is also ephemeral in Easel Functions.

Use a supported external storage driver for durable application data:

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    storage: {
      sessions: {
        driver: "redis",
        url: process.env.REDIS_URL,
      },
    },
  },
});

Use Easel’s Runtime Cache for framework response caching and an external database or storage service for durable application state.

Images

Static image files in public/ are deployed directly to the Easel CDN:

<template>
  <img
    src="/images/hero.jpg"
    alt="Product dashboard"
    width="1600"
    height="900"
  />
</template>

Nuxt Image can be added with:

npx nuxt module add image

Then use <NuxtImg> or <NuxtPicture>:

<template>
  <NuxtImg
    src="/images/hero.jpg"
    alt="Product dashboard"
    width="1600"
    height="900"
    sizes="100vw md:1200px"
  />
</template>

@nuxt/image supports several built-in and third-party providers. Compatibility depends on the provider selected by the application.

Static source assets and remote providers that generate external image URLs do not require Easel-side transformation. Providers that expect a persistent local image server or provider-specific runtime behavior must be validated in a preview deployment.

Consult the image provider’s documentation before relying on dynamic image transformation in production.

Streaming

Nitro server routes can return streaming responses:

server/api/stream.get.ts
export default defineEventHandler(() => {
  const encoder = new TextEncoder();

  return new ReadableStream({
    async start(controller) {
      controller.enqueue(encoder.encode("Starting\n"));

      await new Promise((resolve) => {
        setTimeout(resolve, 500);
      });

      controller.enqueue(encoder.encode("Finished\n"));

      controller.close();
    },
  });
});

Easel forwards response chunks as they are produced.

Streaming keeps the function invocation active until the stream closes. Long-running streams remain subject to the function’s maximum duration and connection limits.

Use a persistent service for indefinite connections.

Scheduled tasks

Nitro scheduled tasks are not currently registered automatically as Easel schedules.

Expose the work through an authenticated server route:

server/api/internal/cleanup.post.ts
export default defineEventHandler(async (event) => {
  const authorization = getRequestHeader(event, "authorization");

  if (authorization !== `Bearer ${process.env.CRON_SECRET}`) {
    throw createError({
      statusCode: 401,
      statusMessage: "Unauthorized",
    });
  }

  await removeExpiredRecords();

  return {
    completed: true,
  };
});

Invoke that route using an external scheduler.

The handler should be idempotent and should authenticate every scheduled request.

WebSockets

Easel Functions do not currently accept long-lived WebSocket upgrades.

Do not rely on Nitro WebSocket handlers for application traffic deployed through the standard Nuxt function path.

Use a dedicated WebSocket service or persistent application service for bidirectional connections.

Streaming HTTP responses and Server-Sent Events use a different transport and can work within function duration and connection limits.

Local filesystem

The function filesystem is ephemeral.

Reading files bundled into the deployment is supported:

const contents = await useStorage("assets:server").getItem(
  "templates/email.html",
);

Files created during an invocation are not guaranteed to exist on a later invocation or on another function instance.

Use object storage, a database, or another durable service for:

  • User uploads
  • Generated documents
  • Session data
  • Application state
  • Persistent caches

Project configuration

Easel detects conventional Nuxt settings automatically:

SettingDefault
Install commandDetected from the package manager
Build commandPackage script or nuxt build
Output directoryDetected from Nitro output
Development commandPackage script or nuxt dev
Node.js versionProject or platform default

For monorepos, set the project root to the directory containing the Nuxt application’s package.json and nuxt.config file.

Do not commit .output as a substitute for running the production build unless your deployment workflow intentionally uses a prebuilt artifact.

Local development

Continue using Nuxt’s normal development server:

npm run dev

Nuxt development uses Nitro’s development runtime and provides hot module replacement for pages, components, server routes, and middleware.

Use an Easel preview deployment to validate platform-specific behavior, including:

  • Production Nitro output
  • Function execution
  • Route-rule caching
  • Prerendered routes
  • Streaming
  • Runtime environment variables
  • Function duration and resource limits
  • WAF and security rules

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

Known limitations

WebSockets

Long-lived WebSocket upgrades are not supported by the standard Easel Functions deployment path.

Scheduled tasks

Nitro tasks are not converted automatically into Easel schedules. Use an authenticated server route and an external scheduler.

Persistent local storage

Memory and local filesystem storage are scoped to individual function instances and may be discarded at any time.

Image providers

@nuxt/image provider compatibility varies. Static images work normally, but dynamic providers that depend on platform-specific transformation infrastructure must be tested individually.

Provider-specific Nitro features

Features designed specifically for another Nitro deployment provider may not behave the same way on Easel.

Do not manually select another provider’s Nitro preset unless you are intentionally producing a prebuilt artifact whose output Easel supports.

Experimental features

Experimental Nuxt and Nitro features can change without maintaining the same deployment output. Validate them with a preview deployment before using them in production.

Troubleshooting

Easel cannot find the server entry

Make sure the project runs nuxt build, not a custom command that replaces or moves the .output directory.

Remove manually configured NITRO_PRESET, SERVER_PRESET, or nitro.preset values unless they are specifically required by your deployment workflow.

The project deploys without server rendering

Confirm that the build command is nuxt build rather than nuxt generate.

Also check that ssr: false is not set globally or for the affected route.

A route rule does not apply

Confirm that the path pattern matches the deployed URL and that a more specific rule is not overriding it.

When using inline route rules, verify that the Nuxt experimental option required by your framework version is enabled. Define the rule in nuxt.config when the page path cannot be translated into a route-rule pattern.

A server environment variable is undefined

Confirm that the variable is configured for the current Easel environment.

For Nuxt runtime configuration, use the corresponding NUXT_ environment-variable name. Preview and production environments can have different values.

Cached content does not update

Check the applicable cache, swr, or isr route rule and confirm that the route is being served through the intended rendering path.

Deploy a preview and inspect the response headers, function logs, and cache activity.

Prerendering misses a route

Routes must either be listed explicitly, discovered through crawler links, or added programmatically during the build.

Dynamic routes that are not linked from another prerendered page generally need to be supplied explicitly.

Code works locally but fails after deployment

Run a preview deployment and inspect the production build and function logs.

Common causes include:

  • Missing runtime environment variables
  • Case-sensitive file paths
  • Native dependencies incompatible with the function runtime
  • Reliance on persistent local files
  • A manually selected Nitro preset
  • Build-time services that are inaccessible from Easel
  • Provider-specific Nuxt modules

Compatibility policy

Easel tests its Nuxt integration against representative applications covering:

  • Server-side rendering
  • Client-side rendering
  • Static generation
  • Hybrid route rules
  • API routes
  • Server middleware
  • Nitro plugins
  • Streaming
  • Runtime configuration
  • Prerendering
  • Response caching
  • Redirects and headers

Stable Nuxt 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 Nuxt version, Nitro version, module, image provider, or experimental feature, create a preview deployment before upgrading the production application.