diff --git a/.changeset/add-rwsdk-support.md b/.changeset/add-rwsdk-support.md
new file mode 100644
index 0000000..145ec9e
--- /dev/null
+++ b/.changeset/add-rwsdk-support.md
@@ -0,0 +1,5 @@
+---
+"@navita/vite-plugin": minor
+---
+
+Add rwsdk (RedwoodJS SDK) support via new `@navita/vite-plugin/rwsdk` export
diff --git a/documentation/200-integrations/240-rwsdk.md b/documentation/200-integrations/240-rwsdk.md
new file mode 100644
index 0000000..8a6e74f
--- /dev/null
+++ b/documentation/200-integrations/240-rwsdk.md
@@ -0,0 +1,88 @@
+---
+title: rwsdk
+description:
+---
+
+# rwsdk
+
+A plugin for integrating Navita with [rwsdk](https://rwsdk.com/) (RedwoodJS SDK).
+
+> Navita has first-class support for rwsdk! It works with Cloudflare Workers SSR! 🎉
+
+## Installation
+
+```bash
+npm install --save-dev @navita/vite-plugin
+```
+
+## Setup
+
+Add the plugin to your Vite configuration, along with any desired [plugin configuration](#configuration).
+
+```js
+// vite.config.mts
+import { defineConfig } from "vite";
+import { redwood } from "rwsdk/vite";
+import { cloudflare } from "@cloudflare/vite-plugin";
+import { navitaRwsdk } from "@navita/vite-plugin/rwsdk";
+
+export default defineConfig({
+ plugins: [
+ navitaRwsdk({
+ // configuration
+ }),
+ cloudflare({ viteEnvironment: { name: "worker" } }),
+ redwood(),
+ ],
+});
+```
+
+Please note that the import is different from the Vite plugin. The rwsdk plugin is based on, and uses the Vite plugin,
+but we've added some extra functionality to make it better suited for rwsdk's build architecture.
+
+### Using Navita styles
+
+In your `Document.tsx`, add a link to the virtual Navita stylesheet:
+
+```tsx
+// src/Document.tsx
+export const Document: React.FC<{ children: React.ReactNode }> = ({ children }) => (
+
+
+
+
+
+
+
{children}
+
+
+
+);
+```
+
+The plugin will automatically replace the virtual path with the hashed CSS file path during the build process.
+
+## How it works
+
+rwsdk has a unique build architecture with three passes:
+
+1. **Client build**: Standard Vite client build that outputs assets to `dist/client/`
+2. **Worker build**: Server-side worker bundle that renders React components
+3. **Linker pass**: A special build pass that processes the worker bundle and replaces asset placeholders with hashed paths from the client manifest
+
+The base Navita plugin works correctly for the client build - CSS is emitted and registered in the Vite manifest.
+The rwsdk plugin adds a `renderChunk` hook that runs during the linker pass to replace `/virtual:navita.css` references
+with the actual hashed filename from the client manifest.
+
+## Configuration
+
+> The rwsdk plugin is based on the Vite plugin, so you can use the same configuration options.
+
+The plugin accepts the following as optional configuration:
+
+#### `importMap?: { callee: string, source: string }[]`
+*Default*: `import { importMap } from "@navita/css"`
+
+ImportMap is a feature that allows you to extend the functionality of Navita.
+
+If you provide your own, it will be merged with the default importMap.
diff --git a/packages/vite-plugin/package.json b/packages/vite-plugin/package.json
index b987b53..2d41842 100644
--- a/packages/vite-plugin/package.json
+++ b/packages/vite-plugin/package.json
@@ -19,6 +19,11 @@
"import": "./dist/remix.mjs",
"require": "./dist/remix.cjs",
"types": "./src/remix.ts"
+ },
+ "./rwsdk": {
+ "import": "./dist/rwsdk.mjs",
+ "require": "./dist/rwsdk.cjs",
+ "types": "./src/rwsdk.ts"
}
},
"publishConfig": {
diff --git a/packages/vite-plugin/src/rwsdk.ts b/packages/vite-plugin/src/rwsdk.ts
new file mode 100644
index 0000000..4445f15
--- /dev/null
+++ b/packages/vite-plugin/src/rwsdk.ts
@@ -0,0 +1,94 @@
+import * as fsp from "node:fs/promises";
+import * as path from "node:path";
+import type { Plugin } from "vite";
+import type { Options } from "./index";
+import { navita, VIRTUAL_MODULE_ID } from "./index";
+
+export function navitaRwsdk(options?: Options): Plugin[] {
+ let projectRootDir: string;
+ let base: string;
+
+ const navitaPlugin = navita(options);
+
+ return [
+ navitaPlugin,
+ {
+ name: "navita-rwsdk",
+ enforce: "post",
+
+ configResolved(config) {
+ projectRootDir = config.root;
+ base = config.base;
+ },
+
+ async renderChunk(code) {
+ // Only run during the linker pass on the worker environment
+ // @ts-expect-error - environment exists at runtime in Vite 6+
+ const environmentName = this.environment?.name;
+
+ if (environmentName !== "worker" || process.env.RWSDK_BUILD_PASS !== "linker") {
+ return null;
+ }
+
+ // Read the client manifest to find the navita CSS path
+ const manifestPath = path.resolve(
+ projectRootDir,
+ "dist",
+ "client",
+ ".vite",
+ "manifest.json"
+ );
+
+ let manifestContent: string;
+ try {
+ manifestContent = await fsp.readFile(manifestPath, "utf-8");
+ } catch {
+ console.warn("[navita-rwsdk] Could not read client manifest, skipping CSS replacement");
+ return null;
+ }
+
+ const manifest = JSON.parse(manifestContent) as Record;
+
+ // Find the navita CSS file in the manifest
+ let navitaCssPath: string | null = null;
+
+ for (const [key, value] of Object.entries(manifest)) {
+ // Check if this is the navita CSS entry directly
+ if (key.includes("navita") && key.endsWith(".css")) {
+ navitaCssPath = (base || "/") + value.file;
+ break;
+ }
+ // Also check if it's referenced in the css array of any entry
+ if (value.css) {
+ for (const cssFile of value.css) {
+ if (cssFile.includes("navita")) {
+ navitaCssPath = (base || "/") + cssFile;
+ break;
+ }
+ }
+ if (navitaCssPath) break;
+ }
+ }
+
+ if (!navitaCssPath) {
+ console.warn("[navita-rwsdk] Could not find navita CSS in manifest");
+ return null;
+ }
+
+ // Replace virtual:navita.css references with the actual hashed path
+ let newCode = code;
+ newCode = newCode.replaceAll(`/${VIRTUAL_MODULE_ID}`, navitaCssPath);
+ newCode = newCode.replaceAll(VIRTUAL_MODULE_ID, navitaCssPath);
+
+ if (newCode !== code) {
+ return { code: newCode, map: null };
+ }
+
+ return null;
+ },
+ },
+ ];
+}