1---
2title: Single Stack
3description: One alchemy.run.ts at the workspace root deploys every app in the monorepo — one plan, one state file, and every path anchored to the file that declares it.
4---
5
6One `alchemy.run.ts` at the workspace root deploys every app in the monorepo
7([`examples/monorepo-single-stack`](https://github.com/alchemy-run/alchemy/tree/main/examples/monorepo-single-stack)):
8
9```sh
10.
11├── package.json # workspaces: ["frontend", "backend"]
12├── alchemy.run.ts # the one Stack — deploys both apps
13├── backend/
14│ └── src/
15│ └── Service.ts # the Worker — this file is its own entry
16└── frontend/
17 ├── index.html # Vite app
18 └── src/
19 └── main.tsx
20```
21
22The entire Stack:
23
24```typescript
25// alchemy.run.ts
26import * as Alchemy from "alchemy";
27import * as Cloudflare from "alchemy/Cloudflare";
28import * as Effect from "effect/Effect";
29import { Path } from "effect/Path";
30import Service from "./backend/src/Service.ts";
31
32export default Alchemy.Stack(
33 "Monorepo",
34 {
35 providers: Cloudflare.providers(),
36 state: Cloudflare.state(),
37 },
38 Effect.gen(function* () {
39 const backend = yield* Service;
40 const path = yield* Path;
41
42 const website = yield* Cloudflare.Website.Vite("Website", {
43 rootDir: path.resolve(import.meta.dirname, "frontend"),
44 env: {
45 VITE_API_URL: backend.url.as<string>(),
46 },
47 });
48
49 return {
50 backendUrl: backend.url.as<string>(),
51 websiteUrl: website.url.as<string>(),
52 };
53 }),
54);
55```
56
57Run `alchemy deploy` (or `alchemy dev`) from the workspace root and both apps
58go up in one plan, tracked in one state file. The rest of this page explains
59the two lines that make the monorepo shape work: `main: import.meta.url` and
60`rootDir`.
61
62## Anchor every path
63
64:::caution
65`alchemy deploy` and `alchemy dev` run from the workspace root, so **relative
66paths resolve from the root**, not from the file that declares the resource.
67A Worker declared in `backend/` with `main: "src/worker.ts"` resolves to
68`<root>/src/worker.ts` — a file that doesn't exist.
69:::
70
71Anchor every path to the file that declares it, using `import.meta`:
72
73| Declaring | Use |
74| ------------------------------------------ | ------------------------------------------------------- |
75| a Worker whose defining file is its entry | `main: import.meta.url` |
76| a Worker with a separate entry file | `main: new URL("./src/worker.ts", import.meta.url).href` |
77| a Vite website in a subdirectory | `rootDir: path.resolve(import.meta.dirname, "frontend")` |
78| a custom Worker entry inside a Vite app | `vite: { main: "src/worker.ts" }` — resolves from `rootDir` |
79
80`import.meta.url` and `import.meta.dirname` always point at the file being
81evaluated, so the resource resolves the same way no matter which directory
82`alchemy` runs from.
83
84## The backend Worker
85
86Each app declares its own Worker in its own package:
87
88```typescript
89// backend/src/Service.ts
90import * as Cloudflare from "alchemy/Cloudflare";
91import * as Effect from "effect/Effect";
92import * as HttpServerResponse from "effect/http/HttpServerResponse";
93
94export default class Service extends Cloudflare.Worker<Service>()(
95 "Service",
96 { main: import.meta.url },
97 Effect.gen(function* () {
98 return {
99 fetch: Effect.succeed(HttpServerResponse.text("Hello from the backend")),
100 };
101 }),
102) {}
103```
104
105`main: import.meta.url` makes this file its own entry — a file URL, absolute
106by construction, so the root `alchemy.run.ts` can import `Service` from
107anywhere and bundling still finds the right module.
108
109The example serves a typed `HttpApi` instead of a plain `fetch` handler and
110shares its typed client with the frontend — see
111[Effect HTTP API](/cloudflare/apis/effect-http-api) for that pattern.
112
113## The Vite website
114
115```typescript
116const website = yield* Cloudflare.Website.Vite("Website", {
117 rootDir: path.resolve(import.meta.dirname, "frontend"),
118});
119```
120
121`rootDir` is Vite's `root` — where `index.html`, `vite.config.ts`, and the
122app source live. It defaults to `process.cwd()`, which in a monorepo is the
123workspace root, so point it at the app's directory explicitly. Anchoring with
124`import.meta.dirname` keeps it correct regardless of where `alchemy` runs.
125
126## Wire the Worker's URL into the frontend
127
128```typescript
129const website = yield* Cloudflare.Website.Vite("Website", {
130 rootDir: path.resolve(import.meta.dirname, "frontend"),
131 env: {
132 VITE_API_URL: backend.url.as<string>(),
133 },
134});
135```
136
137`backend.url` is an `Output<string>` — passing it into `env` makes Alchemy
138deploy the Worker first, then build the website with the resolved URL baked
139in. The frontend reads it like any other Vite env var:
140
141```typescript
142// frontend/src/main.tsx
143const API_URL = import.meta.env.VITE_API_URL;
144```
145
146This is the payoff of the single-stack shape: the frontend consumes the
147backend's URL directly off the in-memory Output — no cross-stack references,
148no deploy ordering to manage.
149
150## Custom Worker entry inside the Vite app
151
152By default `Website.Vite` deploys the server entry your framework produces.
153If the app ships its own Worker module (extra handlers, Durable Objects),
154point `vite.main` at it:
155
156```typescript
157const website = yield* Cloudflare.Website.Vite("Website", {
158 rootDir: path.resolve(import.meta.dirname, "frontend"),
159 vite: {
160 main: "src/worker.ts",
161 },
162});
163```
164
165`vite.main` is the one relative path that does *not* resolve from the
166workspace root — it resolves from `rootDir`, so `src/worker.ts` means
167`frontend/src/worker.ts`.
168
169## Add more apps
170
171The pattern scales by repetition: declare each app's Worker in its own
172package with an anchored `main`, import them all into the one Stack, and
173`yield*` each one. A Worker whose entry is a separate file anchors it with
174`new URL`:
175
176```typescript
177// apps/api/Api.ts
178import * as Cloudflare from "alchemy/Cloudflare";
179
180export const Api = Cloudflare.Worker("Api", {
181 main: new URL("./src/worker.ts", import.meta.url).href,
182});
183```
184
185```typescript
186// alchemy.run.ts
187const api = yield* Api;
188```
189
190## Deploy
191
192```sh
193alchemy deploy
194```
195
196One plan, one apply, one state file. Both apps go up together; both come
197down together with `alchemy destroy`.
198
199## Where next
200
201- [Monorepo](/project-structure/monorepo) — the high-level chooser.
202- [Multiple Stacks](/project-structure/monorepo-multi-stack) — split the
203 deploy per package when cadences diverge.
204- [File layout](/project-structure/file-layout) — the single-package layout
205 this builds on.
206- [Effect HTTP API](/cloudflare/apis/effect-http-api) — share a typed
207 `HttpApi` schema and client between the Worker and the frontend.