Skip to content

Hetzner.Website reference

Source: src/Hetzner/Website/Astro.ts

Deploy an Astro application to a Hetzner Cloud Server: the Node adapter bundle as a systemd unit on port 3000, static assets baked into the unit. Pages render on demand by default (output: "server"); astro: { output: "static" } prerenders every page and deploys assets-only.

The build runs through @alchemy.run/frontend-frameworks/astro with the @alchemy.run/frontend-frameworks/astro/node deploy target. Your astro.config.* must not declare an adapter.

Basic Astro App

const site = yield* Hetzner.Website.Astro("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.Astro("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});
const site = yield* Hetzner.Website.Astro("Docs", {
rootDir: "./docs",
astro: { output: "static" },
assets: { notFoundHandling: "404-page" },
});

Source: src/Hetzner/Website/Foldkit.ts

Deploy a Foldkit app to a Hetzner Cloud Server.

Foldkit apps are client-only Vite projects, so this composite is the Vite site with SPA fallback to index.html (deep links boot the app and the Foldkit router takes over).

Foldkit App

const site = yield* Hetzner.Website.Foldkit("Website");

Project in a Subdirectory

const site = yield* Hetzner.Website.Foldkit("Website", {
rootDir: "applications/web",
});
const site = yield* Hetzner.Website.Foldkit("Website", {
assets: { notFoundHandling: "404-page" },
});

Source: src/Hetzner/Website/Nextjs.ts

Deploy a Next.js application to a Hetzner Cloud Server: next build then a long-running next({ dev: false }) systemd unit on port 3000. Does not use OpenNext (those wrappers are Lambda/workerd).

The .next output (and public/ when present) is packed into the unit archive. next, react, and react-dom are installed on the unit with npm install rather than bundled.

During alchemy dev the site is next dev and no cloud resources are declared; Alchemy.remote() opts back into the live Service.

Basic Next.js App

const site = yield* Hetzner.Website.Nextjs("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.Nextjs("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});

Source: src/Hetzner/Website/Nuxt.ts

Deploy a Nuxt application to a Hetzner Cloud Server: the nitro Node server as a systemd unit on port 3000, static assets baked into the unit.

The build runs through @alchemy.run/frontend-frameworks/nuxt with the @alchemy.run/frontend-frameworks/nuxt/node deploy target (nitro.preset "node").

Basic Nuxt App

const site = yield* Hetzner.Website.Nuxt("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.Nuxt("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});

Source: src/Hetzner/Website/Octane.ts

Deploy an OctaneJS application to a Hetzner Cloud Server: Octane’s SSR server as a systemd unit on port 3000, static assets baked into the unit.

Hetzner.Website.Octane selects hosting and automatically wraps Octane’s default native Node output. Keep compiler and route settings in octane.config.ts without an adapter. The legacy Node marker adapter remains optional for existing projects.

Basic Octane App

const site = yield* Hetzner.Website.Octane("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.Octane("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});

Source: src/Hetzner/Website/ReactRouter.ts

Deploy a React Router v7 app (framework mode) to a Hetzner Cloud Server: the SSR server as a systemd unit on port 3000, static assets baked into the unit.

The build runs through @alchemy.run/frontend-frameworks/react-router with the @alchemy.run/frontend-frameworks/react-router/node deploy target — both must be installed in your project, alongside @react-router/dev, react-router, and vite.

Your vite.config.ts needs no adapter wiring. React Router’s server build is a ServerBuild manifest rather than a request handler, so the integration makes the server pass’s build input a module that wraps the manifest with createRequestHandler and packages the resulting fetch handler as a Node HTTP server.

React Server Components (React Router’s unstable RSC plugin) and multi-environment builds are not supported yet — the build fails with an actionable error when more than one server entry is emitted.

Basic React Router App

const site = yield* Hetzner.Website.ReactRouter("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.ReactRouter("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});

Process Environment

const site = yield* Hetzner.Website.ReactRouter("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});

Read An Environment Variable From A Loader

app/routes/home.tsx
export function loader() {
return { apiBase: process.env.API_BASE ?? "unset" };
}

Source: src/Hetzner/Website/SolidStart.ts

Deploy a SolidStart application to a Hetzner Cloud Server: the SSR server as a systemd unit on port 3000, static assets (prerendered pages included) baked into the unit.

The build runs through @alchemy.run/frontend-frameworks/solidstart with the @alchemy.run/frontend-frameworks/solidstart/node deploy target — both must be installed in your project, alongside @solidjs/start and @solidjs/vite-plugin-nitro-2.

Your vite.config.ts needs no adapter wiring: the integration drives the project’s own vite build and appends its own nitro plugin instance carrying nitro’s node preset.

Basic SolidStart App

const site = yield* Hetzner.Website.SolidStart("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.SolidStart("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});
const site = yield* Hetzner.Website.SolidStart("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});

The integration owns the nitro plugin instance (a nitroV2Plugin() in your vite.config.* is rejected), so nitro options — prerendering included — go on the nitro prop. Prerendered pages are baked into the unit and served as static assets.

const site = yield* Hetzner.Website.SolidStart("Web", {
rootDir: "./app",
nitro: { prerender: { routes: ["/", "/about"] } },
});

Source: src/Hetzner/Website/StaticSite.ts

A Hetzner Service that serves static assets built by a shell command.

StaticSite runs a build command (e.g. npm run build), packs the output directory into the unit archive, and deploys a tiny static-file server (GET assets, /health, optional SPA / 404-page). Use this when your site has its own build step — Hugo, Zola, Eleventy, or any custom pipeline.

For Vite-based projects, prefer Hetzner.Website.Vite.

Dev and Build carry constant logical ids, so they are namespaced under id. The Service stays in the caller’s namespace (same as Cloudflare.Website.StaticSite).

Deploying a Hugo site

const site = yield* Hetzner.Website.StaticSite("Blog", {
command: "hugo --minify",
outdir: "public",
});

SPA-style routing

const site = yield* Hetzner.Website.StaticSite("App", {
command: "npm run build",
outdir: "dist",
spa: true,
});
const site = yield* Hetzner.Website.StaticSite("Web", {
cwd: "apps/web",
command: "npm run build",
outdir: "dist",
});
const site = yield* Hetzner.Website.StaticSite("App", {
command: "npm run build",
outdir: "dist",
dev: { command: "npm run dev" },
});

Source: src/Hetzner/Website/SvelteKit.ts

Deploy a SvelteKit application to a Hetzner Cloud Server: kit’s SSR server as a systemd unit on port 3000, static assets (prerendered pages included) baked into the unit.

The build runs through @alchemy.run/frontend-frameworks/sveltekit with the @alchemy.run/frontend-frameworks/sveltekit/node deploy target.

Basic SvelteKit App

const site = yield* Hetzner.Website.SvelteKit("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.SvelteKit("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});

Source: src/Hetzner/Website/TanStackStart.ts

Deploy a TanStack Start application to a Hetzner Cloud Server: the SSR server as a systemd unit on port 3000, static assets baked into the unit.

The build runs through @alchemy.run/frontend-frameworks/tanstack-start with the @alchemy.run/frontend-frameworks/tanstack-start/node deploy target — both must be installed in your project, alongside @tanstack/react-start (or @tanstack/solid-start) and vite.

Your vite.config.ts needs no adapter wiring: TanStack Start is pure Vite, so the integration drives the project’s own vite build, forces the SSR bundle to be self-contained, and wraps its fetch handler as a Node HTTP server.

TanStackStart: Creating TanStack Start Sites

Section titled “TanStackStart: Creating TanStack Start Sites”

Basic TanStack Start App

const site = yield* Hetzner.Website.TanStackStart("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.TanStackStart("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});

Process Environment

const site = yield* Hetzner.Website.TanStackStart("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});

Read An Environment Variable From A Server Function

src/routes/index.tsx
const getApiBase = createServerFn({ method: "GET" }).handler(() => ({
apiBase: process.env.API_BASE,
}));

Source: src/Hetzner/Website/Vinext.ts

Deploy a vinext application to a Hetzner Cloud Server: vinext’s Vite build then a long-running startProdServer systemd unit on port 3000. Does not use the Cloudflare Worker entry (vinext/server/fetch-handler).

The dist/ output is packed into the unit archive. vinext, react, react-dom, and react-server-dom-webpack are installed on the unit with npm install rather than bundled.

During alchemy dev the site is vinext dev and no cloud resources are declared; Alchemy.remote() opts back into the live Service.

Basic vinext App

const site = yield* Hetzner.Website.Vinext("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.Vinext("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});

Redis data cache

ISR / "use cache" default to in-process memory. Set env.REDIS_URL on this resource for a durable Redis store; the adapter is injected automatically.

const site = yield* Hetzner.Website.Vinext("Web", {
env: {
REDIS_URL: "redis://cache.internal:6379",
},
});

Source: src/Hetzner/Website/Vite.ts

Deploy a plain Vite application to a Hetzner Cloud Server: vite build output served by a static-file systemd unit on port 3000. For client-only projects — React/Vue/Solid SPAs, index.html multi-page apps — whose entire deployable output is static assets.

The build runs through @alchemy.run/frontend-frameworks/vite with the @alchemy.run/frontend-frameworks/vite/node deploy target — the package must be installed in your project. During alchemy dev the site is Vite’s own dev server and no cloud resources are created.

Basic Vite SPA

const site = yield* Hetzner.Website.Vite("Web");

Project in a Subdirectory

const site = yield* Hetzner.Website.Vite("Web", {
rootDir: "./app",
});

Existing Server

const server = yield* Hetzner.Server("box", {
serverType: "cpx12",
image: "ubuntu-24.04",
location: "fsn1",
});
const site = yield* Hetzner.Website.Vite("Web", { server });
const site = yield* Hetzner.Website.Vite("Docs", {
assets: { notFoundHandling: "404-page" },
});
const site = yield* Hetzner.Website.Vite("Web", {
domain: "app.example.com",
zone,
});
const site = yield* Hetzner.Website.Vite("Docs", {
outDir: "build",
base: "/docs/",
});
// `alchemy dev` starts `vite` programmatically: site.url is the local
// dev server (HMR included); no Server or Service is created.
const site = yield* Hetzner.Website.Vite("Web");

Source: src/Hetzner/Website/Vocs.ts

Deploy a Vocs documentation project to a Hetzner Cloud Server. Static assets first, then Vocs’ Waku RSC handler (/aboutabout/index.html, otherwise SSR).

Vocs Documentation Site

const docs = yield* Hetzner.Website.Vocs("Docs", {
rootDir: "./docs",
});

Custom Output Directory

const docs = yield* Hetzner.Website.Vocs("Docs", {
rootDir: "./docs",
outDir: "build",
});

Source: src/Hetzner/Website/Waku.ts

Deploy a Waku application to a Hetzner Cloud Server: the RSC server as a systemd unit on port 3000, static assets (SSG pages included) baked into the unit. Prerendered pages are served extensionless (/about not /about/).

Basic Waku App

const site = yield* Hetzner.Website.Waku("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Hetzner.Website.Waku("Web", {
rootDir: "./app",
domain: "app.example.com",
zone,
});