A static site generator (SSG) takes your content — Markdown posts, a few templates — and builds it into plain HTML you can host anywhere. You get the authoring comfort of templates and a blog structure, with the speed and simplicity of a static site at the end. This guide covers two popular ones, Eleventy and Astro, and the honest workflow for hosting their output on Falix: build, then serve the built folder.
| At a glance | |
|---|---|
| You need | Node.js locally or a Node.js application server to build; any static-capable server to host |
| Background | Host a static website |
| Plan | Any plan |
| Time | Thirty minutes |
The one idea: build produces a folder
Both tools do the same core thing — run a build command that reads your source and writes a folder of finished HTML/CSS/JS:
| Tool | Build command | Output folder |
|---|---|---|
| Eleventy | eleventy (or npx @11ty/eleventy) |
_site/ |
| Astro | astro build |
dist/ |
That output folder is a complete static site — no Node needed to serve it. Which leads to the key decision below.
Two ways to host the output
Build locally, upload the output (recommended). Run the build on your own machine, then drop the finished _site/ or dist/ into the public/ folder of the static-site setup (the PHP Web Server / nginx application), or upload it over SFTP. The server never builds anything — it just serves files, fast and with nothing to break. This is the path to prefer.
Build on the server. Put the build in a postinstall script or a post-deploy command so the Node.js application rebuilds on deploy, then serve the output with a tiny Express server. Convenient with Git deploys, at the cost of build time and memory on every build.
🎯 Good to know: Because the built site is just static files, hosting it is identical to any other static site — the static website guide covers URLs, a custom 404, and giving it a real domain. An SSG only changes how those files are made.
Eleventy — Markdown in, HTML out
Eleventy is the minimalist pick: point it at a folder of Markdown and templates and it writes matching HTML, no config required to start. A tiny project:
.eleventy.js
src/
index.md
_includes/
base.njk
.eleventy.js:
module.exports = function (eleventyConfig) {
return { dir: { input: 'src', output: '_site', includes: '_includes' } };
};
src/_includes/base.njk — a shared layout:
<!doctype html>
<html>
<head><title>{{ title }}</title></head>
<body><header>My site</header><main>{{ content | safe }}</main></body>
</html>
src/index.md — content with front matter picking the layout:
---
title: Home
layout: base.njk
---
# Hello from Eleventy
This page was rendered from Markdown at build time.
Install @11ty/eleventy and run the build:
npx @11ty/eleventy
# [11ty] Writing ./_site/index.html from ./src/index.md
# [11ty] Wrote 1 files in 0.06 seconds (v3.x)
_site/index.html comes out as complete HTML — the layout wrapped around your Markdown, converted to <h1> and <p>. Upload _site/ and you're live. Everything else — collections for a blog, data files, filters — is standard Eleventy, documented at 11ty.dev.
Astro — components, still static output
Astro gives you a component model (and optional React/Vue/Svelte islands) but, with output: 'static', still builds to plain HTML. A minimal project:
astro.config.mjs:
import { defineConfig } from 'astro/config';
export default defineConfig({ output: 'static' });
src/pages/index.astro — front-matter script (between the --- fences) runs at build time; the markup below can loop and interpolate:
---
const title = 'Home';
const items = ['fast', 'static', 'no-JS-by-default'];
---
<html>
<head><title>{title}</title></head>
<body>
<h1>Hello from Astro</h1>
<ul>{items.map((item) => <li>{item}</li>)}</ul>
</body>
</html>
Install astro and build:
npx astro build
# [build] Complete! (pages built in a few hundred ms)
dist/index.html comes out fully rendered — the list expanded at build time, no client JavaScript shipped for a static page. Upload dist/ to host it. Astro's own docs at astro.build cover content collections, islands, and layouts.
💡 Tip:
npm create astroandnpm create eleventyare interactive scaffolders — run them on your own machine, not on the server (the server has no interactive terminal). Then commit the project and deploy from Git, or upload the built output directly.
Building on the server, honestly
If you'd rather the server build (e.g. from Git), the pattern matches Host a Next.js site: a build step plus a small server. Serve the built folder with Express:
const express = require('express');
const app = express();
app.use(express.static('_site')); // or 'dist' for Astro
const PORT = process.env.SERVER_PORT || 8080;
app.listen(PORT, '0.0.0.0', () => console.log(`Listening on port ${PORT}`));
Then run the SSG build in a postinstall script or a post-deploy command so _site/dist exists before the server starts.
⚠️ Heads up: Building on the server costs time and memory on every build. Small sites build in under a second, but a large site with many pages and dependencies is heavier, and a big build can be killed on the free plan's 2.5 GB shared RAM (exit code 137, see Out of memory). If a server build struggles, fall back to the recommended path: build locally, upload the output — the server then only serves files and can't run out of memory building.
Troubleshooting
- Build succeeds but the site 404s — you're serving the wrong folder. Eleventy outputs
_site/, Astro outputsdist/. Point nginx'spublic/orexpress.staticat the right one. command not foundfor eleventy/astro — the tool isn't installed. Usenpx @11ty/eleventy/npx astro build, or add it topackage.jsonand install.- Interactive scaffolder hangs on the server —
npm create ...needs a terminal. Scaffold locally; deploy the result. - Server build killed (137) — out of memory. Build locally and upload
_site/distinstead.
Next steps
- Host a static website — serve the built output
- A blog with Eleventy — a complete build
- Deploy from Git — build on deploy
- Domains and HTTPS — a real address