Skip to content
GitHubXDiscordRSS

Alchemy's Cloudflare Vite Plugin

Alchemy now ships with plugins for Vite, Astro, SvelteKit, Nuxt, React Router, and TanStack Start that streamline local development by eliminating the need for a .dev.vars file, configuration of wrangler state paths, and other boilerplate.

When deploying a Web app in Alchemy, you set secrets using the bindings property:

await Vite("website", {
bindings: {
SOME_SECRET: alchemy.secret.env.SOME_SECRET,
}
})

👍 The alchemy deploy command will build and deploy your website to Cloudflare with the SOME_SECRET as a secure Binding, as expected.

👎 The alchemy dev command, however, would be missing the secret because Cloudflare’s Vite plugin only includes environment variables from .dev.vars.

The workaround was to replicate these values to the .dev.vars file like so:

Terminal window
VITE_SOME_VAR=some-value
SOME_SECRET=some-secret

But, this is redundant and inflexible. As Alchemy users, we want to maintain our configuration and environments in code.

We updated the Website resources (and its variants like Vite, Astro, etc.) to generate a temporary wrangler.json file in .alchemy/local/wrangler.json that contains your secrets in plain text:

{
"name": "website",
"main": "./src/worker.ts",
"compatibility_date": "2025-08-02",
"assets": { "directory": "./dist/client", "binding": "ASSETS" },
"vars": { "SOME_SECRET": "super secret value!" },
}

vite dev will include the vars when running your Worker locally, so you no longer need a .dev.vars file 🎉.

Our new alchemy vite plugin takes care of configuring the annoying boilerplate:

export default defineConfig({
plugins: [
cloudflare({
path: process.env.ALCHEMY_CLOUDFLARE_PERSIST_PATH ,
configPath: ".alchemy/local/wrangler.json",
experimental: { remoteBindings: true }
})
],
plugins: [alchemy()],
});

This blog mostly focused on Vite, but we also vend similar plugins for other frameworks that behave differently than Vite.

astro.config.mjs
import cloudflare from '@astrojs/cloudflare';
import alchemy from 'alchemy/cloudflare/astro';
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
output: 'server',
adapter: cloudflare(),
adapter: alchemy(),
});
svelte.config.mjs
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import adapter from '@sveltejs/adapter-cloudflare';
import alchemy from 'alchemy/cloudflare/sveltekit';
export default {
preprocess: vitePreprocess(),
kit: {
adapter: adapter()
adapter: alchemy()
}
};
nuxt.config.ts
import alchemy from "alchemy/cloudflare/nuxt";
import { defineNuxtConfig } from "nuxt/config";
export default defineNuxtConfig({
compatibilityDate: '2025-05-15',
devtools: { enabled: true },
nitro: {
preset: "cloudflare-module",
cloudflare: {
deployConfig: true,
nodeCompat: true
}
cloudflare: alchemy(),
prerender: {
routes: ["/"],
autoSubfolderIndex: false,
},
},
modules: ["nitro-cloudflare-dev"],
});
vite.config.ts
import { reactRouter } from "@react-router/dev/vite";
import cloudflare from "@cloudflare/vite-plugin";
import alchemy from "alchemy/cloudflare/react-router";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
cloudflare({
viteEnvironment: {
name: "ssr",
},
}),
alchemy(),
reactRouter(),
tsconfigPaths(),
],
});
vite.config.ts
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { cloudflareWorkersDevEnvironmentShim } from "alchemy/cloudflare";
import alchemy from "alchemy/cloudflare/tanstack-start";
import { defineConfig } from "vite";
import tsConfigPaths from "vite-tsconfig-paths";
export default defineConfig({
server: {
port: 3000,
},
build: {
target: "esnext",
rollupOptions: {
external: ["node:async_hooks", "cloudflare:workers"],
},
},
plugins: [
cloudflareWorkersDevEnvironmentShim(),
alchemy(),
tsConfigPaths({
projects: ["./tsconfig.json"],
}),
tanstackStart({
target: "cloudflare-module",
customViteReactPlugin: true,
}),
viteReact(),
],
});