1---
2title: Nuxt
3description: Deploy a Nuxt app to Hetzner with Hetzner.Website.Nuxt — nitro's node preset as a systemd unit on port 3000, and Nuxt's own dev server under alchemy dev.
4sidebar:
5 order: 10
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`Hetzner.Website.Nuxt` deploys a [Nuxt](https://nuxt.com) app to a
13Hetzner Cloud Server. It builds the app through your project's own
14`@nuxt/kit` with nitro's `node` preset: the nitro Node server runs
15as a systemd unit on port 3000, and client assets plus prerendered
16pages are baked into the unit. Omit `server` to get a `cpx12` /
17`ubuntu-24.04` VM in `fsn1`. The live URL is `http://{ipv4}:3000` —
18the Service has no TLS. There is no `nitro.preset` to edit and no
19build command to run.
20
21## Install
22
23The build integration is not bundled with alchemy. Install
24`@alchemy.run/frontend-frameworks`; the resource loads its `/nuxt`
25and `/nuxt/node` exports from your project at deploy time. It is
26only used at build time, so a dev dependency is enough:
27
28<Tabs syncKey="pkgManager">
29 <TabItem label="bun" icon="bun">
30 <Code code={`bun add -d ${devDeps}`} lang="sh" />
31 </TabItem>
32 <TabItem label="npm" icon="npm">
33 <Code code={`npm install -D ${devDeps}`} lang="sh" />
34 </TabItem>
35 <TabItem label="pnpm" icon="pnpm">
36 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
37 </TabItem>
38 <TabItem label="yarn" icon="seti:yarn">
39 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
40 </TabItem>
41</Tabs>
42
43## Configure Nuxt
44
45Your `nuxt.config.ts` loads natively — modules, layers, and all —
46so configure Nuxt exactly as you would outside Alchemy:
47
48```typescript
49// nuxt.config.ts
50export default defineNuxtConfig({
51 modules: ["@nuxtjs/tailwindcss"],
52 routeRules: {
53 "/about": { prerender: true },
54 },
55});
56```
57
58Deploy-specific overrides are merged over it via the `nuxt` prop
59(the override wins) — see [Prerendering](#prerendering) below for an
60example.
61
62Don't set `nitro.preset` — the Node deploy target owns the preset
63(`"node"`), and a foreign preset is a hard error.
64
65## Declare the Website
66
67Declare the site as a module-level const (rather than inline in the
68Stack):
69
70```typescript
71// alchemy.run.ts
72import * as Hetzner from "alchemy/Hetzner";
73
74export const Website = Hetzner.Website.Nuxt("Website");
75```
76
77Pass `rootDir` if `package.json` is not at `.`. Pass `server` to
78run the unit on a Server you already declared — several sites and
79APIs can share one VM. Otherwise the site creates a `cpx12` /
80`ubuntu-24.04` Server in `fsn1`.
81
82```typescript
83export const Box = Hetzner.Server("Box", {
84 serverType: "cpx12",
85 image: "ubuntu-24.04",
86 location: "fsn1",
87});
88
89export const Website = Hetzner.Website.Nuxt("Website", {
90 server: Box,
91});
92```
93
94## Add it to the Stack
95
96Yield the site from your Stack and return its URL:
97
98```typescript
99// alchemy.run.ts
100import * as Alchemy from "alchemy";
101import * as Effect from "effect/Effect";
102
103export default Alchemy.Stack(
104 "MyNuxtSite",
105 {
106 providers: Hetzner.providers(),
107 state: Alchemy.localState(),
108 },
109 Effect.gen(function* () {
110 const site = yield* Website;
111 return { url: site.url };
112 }),
113);
114```
115
116On deploy `site.url` is `http://{ipv4}:3000` (or
117`http://{domain}:3000` when you set `domain`). `site.server` and
118`site.service` are undefined under `alchemy dev`.
119
120## Add environment variables
121
122Process environment is the top-level `env` prop — plain strings, or
123`Redacted` values. They are copied onto `process.env` before build
124and `alchemy dev`, and onto the systemd unit at deploy:
125
126```diff lang="typescript"
127// alchemy.run.ts
128export const Website = Hetzner.Website.Nuxt("Website", {
129+ env: {
130+ GREETING: "Hello from Alchemy!",
131+ NUXT_PUBLIC_API_BASE: "https://api.example.com",
132+ },
133});
134```
135
136This is the hosted Node process, not Worker bindings.
137`NUXT_`-prefixed keys override Nuxt `runtimeConfig` the usual way;
138`NUXT_PUBLIC_` values are inlined into the client at build time.
139
140## Read the environment in server code
141
142The nitro server is a plain Node process, so server routes and SSR
143read the environment from `process.env`:
144
145```typescript
146// server/api/hello.ts
147export default defineEventHandler(() => {
148 return { greeting: process.env.GREETING ?? "hello" };
149});
150```
151
152## Prerendering
153
154Routes marked for prerendering in `routeRules` (or via
155`nitro.prerender`) render at build time into `.output/public` and
156are baked into the unit, served as static files before the nitro
157handler:
158
159```typescript
160export const Website = Hetzner.Website.Nuxt("Website", {
161 nuxt: {
162 routeRules: {
163 "/about": { prerender: true },
164 },
165 },
166});
167```
168
169Nitro's `isr` route rule is implemented only by the Vercel and
170Netlify presets. On the Node preset it is silently ignored at build
171time, and the route renders on demand like any other SSR route. Use
172`prerender` for build-time static routes, or `cache` route rules for
173runtime caching.
174
175## Local dev
176
177```sh
178bun alchemy dev
179```
180
181`alchemy dev` runs Nuxt's own dev server (nitro dev, full HMR)
182instead of deploying; `site.url` is the local address and no
183Hetzner resources are created. Wrap the site in `Alchemy.remote()`
184to deploy the live Server and Service even during dev.
185
186## Custom domain
187
188`domain` requires an existing [`Hetzner.Zone`](/hetzner/networking/dns)
189— the Website does not create a zone. It adds an A RecordSet
190pointing at the Server's public IPv4, and `url` becomes
191`http://{domain}:3000`. There is no TLS on the Service:
192
193```typescript
194export const Dns = Hetzner.Zone("Dns", {
195 name: "example.com",
196});
197
198export const Website = Hetzner.Website.Nuxt("Website", {
199 domain: "app.example.com",
200 zone: Dns,
201});
202```
203
204Passing `domain` without `zone` fails.
205
206## Where next
207
208- [`Hetzner.Website.Nuxt` reference](/providers/hetzner/website/nuxt)
209 — every prop and attribute.
210- [Services](/hetzner/compute/services) and
211 [Servers](/hetzner/compute/servers) — the unit and VM the site
212 creates.
213- [Zones & records](/hetzner/networking/dns) — the Zone `domain`
214 requires.
215- [Setup](/hetzner/setup) — project, API token, and alchemy login.