Static-build frameworks
The grid builds Astro, Vite, SvelteKit, and Nuxt sites from your source. You
do not build them yourself. grid plug recognises the framework, writes a
cloudgrid.yaml with a build: block, sends your source to the grid, and the
grid runs the build and serves the result. Astro is the worked example on this
page; Vite, SvelteKit, and Nuxt follow the same model with a different output
directory.
The frameworks and their output directories
Section titled “The frameworks and their output directories”grid plug detects a static-build framework from the dependency in your
package.json (either dependencies or devDependencies, so a vite that
sits in devDependencies, as it usually does, still counts) and picks the
output directory that framework writes to:
| Framework | Detected from | build.output |
|---|---|---|
| SvelteKit | @sveltejs/kit |
build |
| Astro | astro |
dist |
| Nuxt | nuxt |
.output/public |
| Vite | vite |
dist |
All four become a single static service with a build: block. The output
directory is not a guess you can skip: SvelteKit writes to build, not dist,
so a hand-written yaml that copies Astro’s output: dist onto a SvelteKit
project points the service at the wrong directory. That one does not fail
quietly: after the build the grid checks for <output>/index.html, does not
find dist/index.html, and fails the build with Your build command did not produce dist/index.html, naming the field to fix. Contrast that with the
genuinely silent failure below, which has no build: block and so no such
check.
The rows are checked in that order, and the first dependency present wins.
SvelteKit, Astro, and Nuxt all pull in Vite, so many of these projects also list
vite as a dependency. The specific framework is matched before the generic
vite row, so an Astro project that also depends on vite resolves as Astro
(output: dist), and a SvelteKit project that depends on vite resolves as
SvelteKit (output: build), never as a bare Vite app.
What grid plug writes
Section titled “What grid plug writes”Run grid plug in a bare Astro project (one that has an astro dependency and
no scripts.start) and it generates:
# Generated by `grid plug` auto-init.# One service, sourced from this directory. Edit freely.name: my-siteservices: web: type: static path: / build: command: npm run build output: dist source: path: .No configuration is needed. type: static with a build: block is the whole
model: the grid runs command and serves the output directory. source.path: . says the project is at the folder root, so your existing files are what the
grid builds from.
What happens when you plug
Section titled “What happens when you plug”grid plugpacks your source and sends it to the grid. Your build output directory is left out of that bundle automatically (see The commit-your-build anti-pattern below), so there is nothing to gitignore or stage by hand.- The grid installs dependencies, then runs your build command. With a
package-lock.jsonpresent it runsnpm ci; without one it runsnpm install. See npm only. - The grid serves the
outputdirectory over HTTP at your entity root.
The build runs on the grid, on the source you just sent. The build is async: the
CLI reports progress and prints the live URL when the site is up. To update the
site, change your source and run grid plug again.
The commit-your-build anti-pattern
Section titled “The commit-your-build anti-pattern”The model above builds from source, so there is never a reason to build locally and commit the result. Doing so is the one mistake that fails silently.
Here is the mechanism, because a vague warning will not save you from it:
- With a
build:block, the grid builds from your source and serves its output. Your build output directory is excluded from what gets sent, so a committeddist/is not merely ignored, it never leaves your machine. The live site is always the grid’s own fresh build. - Without a
build:block, astaticservice serves whatever directory you point it at, verbatim. It runs no build. If you pre-build, commitdist/, and write astaticservice around that folder, the grid serves the committed files exactly as they are.
That second shape is where the afternoon goes. You change your source, run grid plug, and it succeeds and prints a working URL. Because no build ran, the site
is still the last dist/ you committed. If you forgot to rebuild and commit, the
live site shows the previous build.
build.env, not grid env set
Section titled “build.env, not grid env set”Build-time variables, the ones your framework reads while it builds (a
VITE_-prefixed API URL, a feature flag baked into the output), go under
build.env:
services: web: type: static path: / build: command: npm run build output: dist env: VITE_API_URL: "https://my-api.your-grid.cloudgrid.io" COMING_SOON: "true"Each entry is set in the environment of the build step, so process.env.VITE_API_URL
resolves while npm run build runs, and the value is baked into the output.
The scripts.start trap
Section titled “The scripts.start trap”Detection runs in a fixed order, and the first rule that matches wins:
- a
nextdependency makes it anextjsservice; - a non-empty
scripts.startmakes it anodeservice, before the static-build check is ever reached; - otherwise, a static-build framework dependency (SvelteKit, Astro, Nuxt, Vite)
makes it a
staticservice with abuild:block.
So a project that has both an astro dependency and a start script becomes a
node service, not a static site. This is deliberate: a framework like Astro or
Vite is often the frontend build tooling sitting next to an Express or Fastify
backend, and that backend has a start script. Classifying it as static would
drop the server entirely. The cost is that it is invisible: if you expect a
static site and your package.json carries a start script, you get a node
service and the difference is silent. Remove the start script, or set the
service type yourself, if you want the static-build path.
npm only
Section titled “npm only”The build step uses npm. It runs npm ci when a package-lock.json is present
and npm install when it is not. pnpm and yarn lockfiles are ignored. A repo
that carries both a pnpm lockfile and an npm lockfile develops against one and
builds against the other, and the two drift apart without any warning. Keep a
package-lock.json committed and current, and treat it as the file the grid
builds from.
Dotenv files and your source
Section titled “Dotenv files and your source”Three dotenv files are always left out of what reaches the grid: .env,
.env.local, and .env.*.local. These are your local machine’s files and never
travel with your source.
.env.example is different: it is sent to the grid, and the grid reads it at
plug time to check that the keys your app expects are accounted for. Keep it
accurate and keep secrets out of it, because it is a committed, sent file and not
a private one. It is reasonable to assume every dotenv file is ignored, so this
is the one to remember: the example travels, the real ones do not.
Where your source lives
Section titled “Where your source lives”By default the grid looks for a service’s code under services/<name>/. The
generated yaml above sets source.path: . instead, which points the service at
the project root, so a single-app repo with the framework at the top level works
without moving anything. Set source.path to any relative directory when your
source lives elsewhere, rather than reshaping your repo to fit the default.
Related
Section titled “Related”- Service types for the full set of service types,
including plain
staticsites with no build step. - cloudgrid.yaml reference for the complete schema.
- Build a documentation site for an Astro Starlight site end to end.