1---
2title: Next.js
3description: "Deploy a Next.js app to Fly with Fly.Website.Nextjs — next build plus a Node next({ dev: false }) Machine, and next dev under alchemy dev."
4sidebar:
5 order: 9
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`Fly.Website.Nextjs` deploys a [Next.js](https://nextjs.org) app to
13Fly as a long-running Node process. It runs a real `next build`, then
14a serve entry that `import("next")` +
15`next({ dev: false }).prepare()` + `getRequestHandler()` on port
163000:
17
18- one [`Fly.App`](/fly/compute/apps) (created if `app` is omitted)
19 and a [`Fly.Service`](/fly/compute/services) running Node on a
20 Machine
21- a shared IPv4 so `https://{app}.fly.dev` answers
22- `.next` and `public/` baked into the image; `next`, `react`, and
23 `react-dom` installed unbundled
24
25This is **not** OpenNext — those wrappers target Lambda and workerd.
26ISR and `next/image` use Next's own Node behavior.
27
28App Router and Pages Router both work — server components, API
29routes, middleware, server actions, dynamic segments, streaming SSR,
30and `getServerSideProps` pages all run in the Node process.
31
32## Install
33
34The build integration is not bundled with alchemy. Install
35`@alchemy.run/frontend-frameworks`; the resource loads the package's
36`/nextjs/node` export from your project at deploy time. It is only
37used at build time, so a dev dependency is enough. Your app still
38needs `next`, `react`, and `react-dom` as usual:
39
40<Tabs syncKey="pkgManager">
41 <TabItem label="bun" icon="bun">
42 <Code code={`bun add -d ${devDeps}`} lang="sh" />
43 </TabItem>
44 <TabItem label="npm" icon="npm">
45 <Code code={`npm install -D ${devDeps}`} lang="sh" />
46 </TabItem>
47 <TabItem label="pnpm" icon="pnpm">
48 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
49 </TabItem>
50 <TabItem label="yarn" icon="seti:yarn">
51 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
52 </TabItem>
53</Tabs>
54
55## Configure Next.js
56
57Your `next.config.*` is loaded and honored as-is — the build runs a
58real `next build`, and Alchemy never rewrites the file. There is no
59adapter to install and no `open-next.config.ts` to write.
60
61Pass `rootDir` when the Next.js project is not the stack root.
62
63## Declare the Website
64
65Declare the site as a module-level const (rather than inline in the
66Stack):
67
68```typescript
69// alchemy.run.ts
70import * as Fly from "alchemy/Fly";
71
72export const Website = Fly.Website.Nextjs("Website");
73```
74
75## Add it to the Stack
76
77Yield the site from your Stack and return its URL:
78
79```typescript
80// alchemy.run.ts
81import * as Alchemy from "alchemy";
82import * as Effect from "effect/Effect";
83
84export default Alchemy.Stack(
85 "MyNextjsSite",
86 {
87 providers: Fly.providers(),
88 state: Alchemy.localState(),
89 },
90 Effect.gen(function* () {
91 const site = yield* Website;
92 return { url: site.url };
93 }),
94);
95```
96
97`url` is `https://{app}.fly.dev` on deploy. `app`, `service`, `ip`,
98and `certificate` are set then and `undefined` under `alchemy dev`.
99
100Pass an existing App to share it with other Services:
101
102```typescript
103export const Website = Fly.Website.Nextjs("Website", { app: Site });
104```
105
106## Add environment variables
107
108Process environment is the top-level `env` prop — plain strings, or
109`Redacted` secrets:
110
111```diff lang="typescript"
112// alchemy.run.ts
113export const Website = Fly.Website.Nextjs("Website", {
114+ env: {
115+ GREETING: "Hello from Alchemy!",
116+ API_BASE: "https://api.example.com",
117+ },
118});
119```
120
121The values are copied onto `process.env` before `next build` /
122`next dev`, and onto the Machine at deploy, so server code and
123`NEXT_PUBLIC_*` inlining see the same map in both modes.
124
125## Read the environment in server code
126
127The Next server is a plain Node process, so route handlers, server
128components, and server actions read the environment from
129`process.env`:
130
131```typescript
132// app/api/hello/route.ts
133export function GET() {
134 return Response.json({ greeting: process.env.GREETING ?? "hello" });
135}
136```
137
138## Local dev
139
140```sh
141bun alchemy dev
142```
143
144`alchemy dev` runs `next dev` (native HMR, Turbopack) instead of
145deploying; `site.url` is the local address and no Fly resources are
146created. Wrap the site in `Alchemy.remote()` to deploy the live
147Service even during dev.
148
149## Custom domain
150
151```typescript
152const site = yield* Fly.Website.Nextjs("Web", {
153 domain: "app.example.com",
154});
155```
156
157Alchemy requests an ACME [`Fly.Certificate`](/fly/networking) on
158the App and `url` becomes `https://app.example.com`. Point DNS at
159the App first — v1 does not create DNS records.
160
161## Where next
162
163- [`Fly.Website.Nextjs` reference](/providers/fly/website/nextjs)
164 — every prop and attribute
165- [Apps](/fly/compute/apps), [Services](/fly/compute/services),
166 [IPs & certificates](/fly/networking)