> This page is for Webflow Cloud.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.webflow.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.webflow.com/_mcp/server.

# Optimizing your app for Webflow Cloud

> How to adapt and optimize supported frameworks for deployment on Webflow Cloud.

Webflow Cloud supports the Next.js, Astro, and Vite frameworks, though running any of them on the Workers runtime may require specific configuration or adjustments. This page offers guidance on framework-specific setup, limitations, and best practices to help you get the most out of your deployment.

> **Environment configuration**
>
> For general deployment and environment configuration, see the [configuration guide](/webflow-cloud/environment/configuration). This page focuses on requirements and recommendations unique to each supported framework.

> **Start from a starter app**
>
> Every framework below has an official starter you can deploy as-is, with a version that has storage bindings wired in. See [Start from a starter app](/webflow-cloud/getting-started#start-from-a-starter-app) for the full list, and [Sentry](/webflow-cloud/sentry) for adding error tracking.

> **No-framework static apps use a different deployment path**
>
> This page covers framework apps.
> For plain HTML, CSS, and JavaScript with no framework build, see [Static apps](/webflow-cloud/static-apps).

## Next.js

Webflow Cloud deploys Next.js apps using the [OpenNext Cloudflare adapter](https://opennext.js.org/cloudflare), an open-source tool that brings Next.js to the edge. It handles translating Next.js's server-side features to the edge runtime, including routing, API routes, static assets, and server functions, enabling modern Next.js apps to run with minimal changes.

To run your Next.js app on Webflow Cloud, you may need to adapt some features and provide custom configuration. See the sections below for details.

### Ready-to-deploy examples

| Example                                                                                                  | Stack                                                     |
| :------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------- |
| [hello-world-next-app](https://github.com/Webflow-Examples/hello-world-next-app/tree/v16)                | Next.js 16 + React 19                                     |
| [with storage bindings](https://github.com/Webflow-Examples/hello-world-next-app/tree/v16-with-bindings) | The same app with SQLite, KV, and object storage wired up |

The `sentry` branch adds a working Sentry setup. See [Sentry](/webflow-cloud/sentry) for the Next.js steps and its Workers-runtime transport caveat.

### Images

Webflow Cloud automatically optimizes images for better performance and reduced bandwidth usage. Here's how different image types are handled:

#### Next.js Image component with local images

When you use the `<Image />` component with local images Webflow Cloud automatically:

* Resizes images for optimal performance
* Serves images through Webflow's CDN for faster loading
* Reduces bandwidth usage

**Example:**

```jsx
import Image from 'next/image';

// This image will be automatically optimized
<Image
  src="/my-image.jpg"
  alt="Description"
  width={500}
  height={300}
/>
```

> **Note**
>
> Webflow Cloud automatically handles routing through your base path when using the Next.js Image component, so you don't need to manually add `assetPrefix` to your image paths.

#### Next.js Image component with external images

When you use the `<Image />` component with external images, including those from your main Webflow site, Webflow Cloud doesn't automatically resize the image. However, you can still benefit from other Image component features like lazy loading.

```jsx
// External images are not automatically optimized
<Image
  src="https://example.com/image.jpg"
  alt="External image"
  width={500}
  height={300}
/>
```

#### Plain `img` tags

When using regular `<img>` tags, no automatic optimization occurs. For assets in your `/public` folder, include `assetPrefix` in the src path to ensure proper CDN caching:

```jsx
// Include assetPrefix for CDN caching
<img src={`${assetPrefix}/my-image.jpg`} alt="Description" />
```

### Limitations

Some Next.js features have limitations when running on Webflow Cloud:

* **Middleware:** [Node.js runtime middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) isn't supported. Only Edge runtime middleware works on the Workers runtime
* **ISR & On-demand revalidation:** Incremental Static Regeneration and on-demand revalidation are experimental
* **Composable caching:** The `use cache` directive isn't yet supported

See the [OpenNext Cloudflare adapter documentation](https://opennext.js.org/cloudflare#supported-nextjs-features) for current feature support.

### Additional resources

* [Cloudflare Adapter for Next.js](https://blog.cloudflare.com/deploying-nextjs-apps-to-cloudflare-workers-with-the-opennext-adapter/)
* [Next.js Edge Runtime Documentation](https://nextjs.org/docs/pages/api-reference/edge)
* [Cloudflare Workers Next.js Guide](https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjs/)
* [OpenNext x Cloudflare Images](https://opennext.js.org/cloudflare/howtos/image)

## Astro

> **Astro 5, 6, and 7 are supported**
>
> Webflow Cloud reads the Astro version from your `package.json` and installs the matching adapter. You don't pin the adapter yourself, and you don't declare the version anywhere.
>
> | Your Astro version | Adapter Webflow Cloud installs | Minimum Node.js |
> | :----------------- | :----------------------------- | :-------------- |
> | Astro 7            | `@astrojs/cloudflare` 14.x     | 22.12           |
> | Astro 6            | `@astrojs/cloudflare` 13.x     | 22.12           |
> | Astro 5            | `@astrojs/cloudflare` 12.x     | 22              |
>
> If you declare a framework in `webflow.json`, use `"astro"` — not a version-specific name.
>
> Astro 5 is the minimum supported version. We recommend the latest major, which is what the app creator wizard provides. On Astro 4 or older? Upgrade before deploying — see Astro's [upgrade guides](https://docs.astro.build/en/upgrade-astro/).

> **Upgrading an existing app to Astro 7**
>
> Deploying on Webflow Cloud doesn't require any Webflow-specific changes when you upgrade — bump `astro` in your `package.json` and Webflow Cloud picks up the matching adapter on your next deployment.
>
> Astro 7 does have its own breaking changes, so work through the [Astro 7 upgrade guide](https://docs.astro.build/en/guides/upgrade-to/v7/) first. Your Node.js version doesn't need to change: Astro 6 already required 22.12, and Astro 7 keeps the same minimum.

### Starting a new app

`webflow cloud init` scaffolds an Astro project for you, so you don't pick a version — the CLI installs a supported one and keeps the template in step with Webflow Cloud.

To control the version yourself, create the project with Astro's own tooling and deploy it as a [bring-your-own-app](/webflow-cloud/bring-your-own-app). Webflow Cloud builds either kind of project the same way: it reads the version from your `package.json` and picks the adapter to match.

### Ready-to-deploy examples

| Example                                                                                                  | Stack                                                                                   |
| :------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
| [hello-world-astro-app](https://github.com/Webflow-Examples/hello-world-astro-app/tree/v7)               | Astro 7 + Tailwind v4                                                                   |
| [with storage bindings](https://github.com/Webflow-Examples/hello-world-astro-app/tree/v7-with-bindings) | Astro 7 with SQLite, KV, and object storage wired up                                    |
| [hello-world-astro-devlink](https://github.com/Webflow-Examples/hello-world-astro-devlink)               | Astro 7, site-attached, with [DevLink](/devlink/reference/overview) components exported |

See [Start from a starter app](/webflow-cloud/getting-started#start-from-a-starter-app) for the full list. Older Astro versions live on the `v5` and `v6` branches of the same repo. The `sentry` branch adds error tracking. See [Sentry](/webflow-cloud/sentry) for the Astro middleware setup.

Webflow Cloud deploys Astro apps using the [@astrojs/cloudflare adapter](https://docs.astro.build/en/guides/integrations-guide/cloudflare/). The adapter supports server-side rendering, API routes, and advanced features like server islands and sessions—translating Astro's server functionality to the Workers runtime for seamless edge deployment.

Most features work out of the box, but some Node.js APIs and integrations may need additional configuration. For details and advanced usage, see the [Astro Cloudflare integration guide](https://docs.astro.build/en/guides/integrations-guide/cloudflare/). However, we've outlined the most common adjustments below.

### Loading React components

Be sure to add the `client:load` directive to your components to load your components.

**`src/pages/index.astro`**

```astro title="src/pages/index.astro" {6}
---
import HelloWorld from '../components/HelloWorld';
---

<div class="container">
  <HelloWorld client:load />
</div>
```

### Static pages

By default, all Astro routes are server-rendered on the edge. For static routes (such as a custom 404 page or privacy policy), enable pre-rendering to generate and serve them as static assets for faster loading.

**`src/pages/404.astro`**

```javascript title="src/pages/404.astro" {2}
---
export const prerender = true; // Pre-render the page for best performance
---
<html>
  <body>
    <h1>404: Not Found</h1>
    <p>This page does not exist.</p>
  </body>
</html>
```

### Environment variables

Astro provides several ways to access environment variables, depending on where your code runs:

* Use `import.meta.env` for built-in variables like `BASE_URL` and `ASSETS_PREFIX`, and for any custom variables prefixed with `PUBLIC_`. Using the `PUBLIC` prefix will make the variable available on both the server and the client.
* Use `Astro.locals.runtime.env`  in Astro server-side components to access custom environment variables.
* Use `locals.runtime.env`  in API routes to access custom environment variables.

To use `locals.runtime.env` variables during local development, create a `dev.vars` file in your app root. Use the same format as a standard `.env` file to define your environment variables.

**`Accessing environment variables in Astro`**

```ts title="Accessing environment variables in Astro"
// 1. Built-in environment variables (available everywhere)
import.meta.env.BASE_URL
import.meta.env.ASSETS_PREFIX

// 2. In Astro components (e.g., src/pages/foo.astro)
Astro.locals.runtime.env.VARIABLE_NAME

// 3. In API routes (e.g., src/pages/api/foo.ts)
import type { APIRoute } from 'astro';

export const GET: APIRoute = async ({ locals }) => {
  const siteId = locals.runtime.env.WEBFLOW_SITE_ID;
  const accessToken = locals.runtime.env.WEBFLOW_API_TOKEN;
  // Use siteId and accessToken as needed
};
```

### API routes

#### Enable Edge runtime for API routes

To ensure Astro API routes work on the [Edge runtime](/webflow-cloud/environment), add the following line to the top of your route:

**`src/routes/api/hello.ts`**

```javascript title="src/routes/api/hello.ts" {1-4}
// Add this line to your route to ensure it runs on the Edge runtime
export const config = {
    runtime: "edge",
};

import type { APIRoute } from 'astro';


export const GET: APIRoute = async ({ locals }) => {
  return new Response(JSON.stringify({ message: 'Hello from the edge!', runtime }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  });
};
```

#### Disable Astro's CSRF protection (if needed)

You may encounter issues with POST requests containing form data. Disable Astro's built-in [CSRF protection](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF) and implement your own CSRF handling.

In your `astro.config.mjs` file, add the following:

**`astro.config.mjs`**

```javascript title="astro.config.mjs" {10}
{
  security: {
    checkOrigin: false,
  },
}
```

### Assets

Astro serves all static assets (such as images, stylesheets, and icons) from the `public` directory. Be sure to place all static files (images, CSS, fonts, etc.) in the `public` directory at your app root.

```
public/
├── images/
│   └── logo.png
├── styles/
│   └── global.css
└── favicon.ico
```

### Tailwind CSS

To use Tailwind CSS in your Astro app, use the `@tailwindcss/vite` plugin to ensure compatibility. Once you've created your Astro app from the CLI, follow the instructions in the [Tailwind CSS integration for Astro](https://tailwindcss.com/docs/installation/framework-guides/astro) guide to set up Tailwind CSS.

> **@astrojs/tailwind is not supported on Webflow Cloud**
>
> `@astrojs/tailwind` is deprecated and not supported on Webflow Cloud.
>
> Third-party templates and guides may still reference it, but it's not recommended to use it. Instead, upgrade to the latest version of Tailwind CSS and use the `@tailwindcss/vite` plugin to ensure compatibility.

## Vite

Webflow Cloud deploys any Vite app — React, Vue, Svelte, vanilla JavaScript, or anything else Vite builds. If your `package.json` has a `vite` dependency and no more specific framework, Webflow Cloud builds it as a Vite app.

> **Vite 6.1 or later is required**
>
> If your app is on an older version, Webflow Cloud upgrades Vite during the build. To avoid surprises, upgrade in your own repo first.

### Ready-to-deploy examples

| Example                                                                              | Stack                     |
| :----------------------------------------------------------------------------------- | :------------------------ |
| [hello-world-react-app](https://github.com/Webflow-Examples/hello-world-react-app)   | React 19 + Vite           |
| [hello-world-vue-app](https://github.com/Webflow-Examples/hello-world-vue-app)       | Vue 3 + Vite              |
| [hello-world-svelte-app](https://github.com/Webflow-Examples/hello-world-svelte-app) | Svelte 5 + Vite           |
| [hello-world-js-app](https://github.com/Webflow-Examples/hello-world-js-app)         | Vanilla JavaScript + Vite |

Each one also has a `-with-bindings` branch with SQLite, KV, and object storage already wired up, for example [hello-world-react-app](https://github.com/Webflow-Examples/hello-world-react-app/tree/v19-with-bindings). For a no-framework static starter, see [Start from a starter app](/webflow-cloud/getting-started#start-from-a-starter-app). The `sentry` branch adds error tracking. See [Sentry](/webflow-cloud/sentry) for the Vite worker setup.

### Base path

Webflow Cloud sets Vite's `base` option to your environment's [mount path](/webflow-cloud/environments#mount-paths) at build time. Don't set `base` yourself — it's overwritten.

Read it through Vite's standard `import.meta.env.BASE_URL`, which reflects whatever `base` was set to:

**`src/api.ts`**

```ts title="src/api.ts"
// BASE_URL is "/" locally and your mount path in production
const response = await fetch(`${import.meta.env.BASE_URL}api/users`);
```

Vite rewrites asset URLs in your HTML, CSS, and imported modules automatically. You only need `BASE_URL` for paths you build yourself, such as `fetch` calls and links constructed as strings.

### Adding API routes

A Vite app is a single-page app by default, and Webflow Cloud serves it as static assets. If you want server-side routes, add a `src/worker.ts` that handles them and falls back to your static assets:

**`src/worker.ts`**

```ts title="src/worker.ts"
/// <reference types="@cloudflare/workers-types" />

interface Env {
  ASSETS: Fetcher;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    if (url.pathname.endsWith('/api/hello')) {
      return Response.json({ message: 'Hello from the edge!' });
    }

    // Anything else falls through to the built SPA
    return env.ASSETS.fetch(request);
  },
} satisfies ExportedHandler<Env>;
```

If you don't provide one, Webflow Cloud generates the asset-serving worker above without the API branch, so a plain SPA needs no worker file at all.

The `ASSETS` binding is provided by the platform and serves your built client output. Your [storage bindings](/webflow-cloud/storing-data/overview) arrive on the same `Env` object.

> **This is a low-level interface**
>
> `src/worker.ts` exposes the underlying runtime handler directly. It's supported and it works today, but it's the most primitive way to add server-side code on Webflow Cloud, and it's the layer most likely to gain a higher-level alternative later.
>
> Keep your route handlers thin and your logic in ordinary modules they call. That way the handler stays a small adapter you can swap out, rather than something your application logic is built into.

### Environment variables

Vite only exposes variables prefixed with `VITE_` to client-side code, and it inlines them into your bundle at build time.

> **Never prefix a secret with \`VITE\_\`**
>
> Anything named `VITE_*` is embedded in the JavaScript you ship to browsers, and marking it secret in Webflow Cloud doesn't change that. Read secrets in `src/worker.ts` from the `Env` object instead, where they stay server-side.

**`src/worker.ts`**

```ts title="src/worker.ts"
interface Env {
  ASSETS: Fetcher;
  API_TOKEN: string; // Set in your Webflow Cloud environment variables
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // env.API_TOKEN never reaches the browser
    return env.ASSETS.fetch(request);
  },
} satisfies ExportedHandler<Env>;
```

### Static assets

Put static files in `public/` at your app root. Vite copies them to the build output as-is and Webflow Cloud serves them through the CDN. Reference them with `BASE_URL`:

```html
<img src={`${import.meta.env.BASE_URL}logo.svg`} alt="Logo" />
```

### Local development

`vite dev` works with no additional setup. For a run against the Workers runtime with your storage bindings connected, install the Cloudflare plugin and Wrangler:

```bash
npm install --save-dev @cloudflare/vite-plugin wrangler
```

Then add the plugin to your config and run `vite dev` as usual. Wrangler emulates the runtime and bindings on your machine, so no Cloudflare account is needed.

**`vite.config.ts`**

```ts title="vite.config.ts"
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { cloudflare } from '@cloudflare/vite-plugin';

export default defineConfig({
  plugins: [react(), cloudflare()],
});
```

Webflow Cloud adds this plugin during its own build whether or not you install it, so adding it locally only changes your development experience.

### Additional resources

* [Cloudflare Vite plugin](https://developers.cloudflare.com/workers/vite-plugin/)
* [Vite `base` option](https://vite.dev/config/shared-options.html#base)
* [Vite environment variables](https://vite.dev/guide/env-and-mode.html)

## Additional resources

* [Deploying Astro to Cloudflare](https://docs.astro.build/en/guides/deploy/cloudflare/)
* [Cloudflare Workers framework guide for Astro](https://developers.cloudflare.com/workers/frameworks/framework-guides/astro/)
* [Hybrid rendering in Astro](https://blog.logrocket.com/hybrid-rendering-astro-guide/)