NOTE:
@fastly/compute-js-static-publishis provided as a Fastly Labs product. Visit the Fastly Labs site for terms of use.
Note
v8 adds Beta support for S3-compatible storage, such as Fastly Object Storage. For more details, see the S3-compatible storage section.
Note
These docs are for v7 or newer, a major rewrite that adds powerful new features such as named collections. If you're looking for v6, please check out the v6 branch.
Warning
v7 and newer no longer support module assets. If you require this feature, consider using v6.
- β¨ Key Features
- π Quick Start
- βοΈ Configuring
static-publish.rc.js - π§Ύ Config for Publishing and Server:
publish-content.config.js - π¦ Collections (Publish, Preview, Promote)
- π§Ή Cleaning Up
- π Content Compression
- π§© Using PublisherServer in Custom Apps
- π₯ Using Published Assets in Your Code
- π CLI Reference
- π Appendix
- π Next Steps
Serve static websites and web apps at the edge β no backends and no CDN invalidation delays.
@fastly/compute-js-static-publish helps you deploy and serve static files using Fastly Compute for fast, cacheable, and content-addressed delivery. The library can be configured to store your data in either of the following:
- Fastly Key Value Store
- S3-compatible storage, such as Fastly Object Storage (Beta)
- π¦ Easy to scaffold and deploy
- π Content stored using hashed keys, in Fastly KV Store or S3-compatible storage (Beta) including Fastly Object Storage
- π Publish new content without needing to deploy new Wasm binaries
- π Organize releases into named collections which can be previewed (e.g.
live,staging,preview-123) - π§Ό Cleanup tools to remove expired or orphaned files
- βοΈ Configurable per-collection server configurations (e.g. fallback files)
- π Supports Brotli/gzip compression, conditional GET, and long cache TTLs
Create a directory for your project, place your static files in ./public, then type:
npx @fastly/compute-js-static-publish@latest \
--root-dir=./public \
--kv-store-name=site-contentYou get a Compute app in ./compute-js with:
fastly.toml(service config)src/index.js(entry point)static-publish.rc.js(app config)publish-content.config.js(publish-time / runtime behavior)
Once the application is scaffolded, install dependencies:
cd compute-js
npm installType the following β no Fastly account or service required yet!
npm run dev:publish
npm run dev:startFastly's local development environment serves your static website at http://127.0.0.1:7676, powered by a simulated KV Store.
Ready to go live? All you need is a free Fastly account!
npm run fastly:deployThe command publishes your Compute app and creates the KV Store. (No content uploaded yet!)
npm run fastly:publishThis command uploads your static files to the KV Store and applies the server config. Your website is now up and live!
Create a directory for your project, place your static files in ./public, then type:
npx @fastly/compute-js-static-publish@latest \
--root-dir=./public \
--storage-mode=s3 \
--s3-region=<s3 region> \
--s3-bucket=<bucket name>Note
If the storage requires a custom endpoint, such as is the case with Fastly Object Storage, specify it using --s3-endpoint=<endpoint>. For example:
npx @fastly/compute-js-static-publish@latest \
--root-dir=./public \
--storage-mode=s3 \
--s3-region=us-east \
--s3-bucket=my-static-content-bucket \
--s3-endpoint=https://us-east.object.fastlystorage.appYou get a Compute app in ./compute-js with:
fastly.toml(service config)src/index.js(entry point)static-publish.rc.js(app config)publish-content.config.js(publish-time / runtime behavior)
Once the application is scaffolded, install dependencies:
cd compute-js
npm installBefore publishing your content, your S3-compatible bucket must already exist.
Tip
If you're using Fastly Object Storage, create your Fastly account if you haven't already, and then follow the Object Storage quick start to set up your bucket.
Type the following:
npm run s3:publishType the following to preview your site locally:
npm run dev:startFastly's local development environment serves your static website at http://127.0.0.1:7676. Your content is fetched from S3-compatible storage.
Ready to go live? All you need is a free Fastly account!
npm run fastly:deployThe app reads the S3 credentials from the Secret Store that has the resource link name S3_CREDENTIALS on your service.
When the Fastly CLI creates a new service, it creates a Secret Store named S3_CREDENTIALS, links it to the service with the same name, and prompts you for the values S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY.
Here's what your project might look like after scaffolding:
my-project/
βββ public/ # Your static site files (HTML, CSS, JS, etc.)
β βββ index.html
β βββ scripts.js
β βββ styles.css
β βββ (... other static files ...)
βββ compute-js/ # The scaffolded Compute application
βββ fastly.toml # Fastly service configuration
βββ package.json # Scaffolded package metadata
βββ package-lock.json # Dependency lockfile
βββ .gitignore # Ignores build artifacts by default
βββ static-publish.rc.js # App config
βββ publish-content.config.js # Publishing / runtime config
βββ static-publisher/ # β οΈ Do not commit - generated content for local dev and publishing
β βββ kvstore.json # Simulates KV Store content for local preview
β βββ kv-store-content/ # Preprocessed and compressed files for KV upload
βββ src/
βββ index.js # Your Compute app entry point
This file defines your compute-js-static-publish application's settings. A copy of this is also baked into the Wasm binary and loaded when running your Compute app locally or on the edge.
Note
Making changes to this file requires rebuilding the Compute app, since a copy of it is baked into the Wasm binary.
All fields are required unless specified otherwise.
storageMode- Specifies the storage mode. Set to'kv-store'.kvStore.kvStoreName- The name of the KV Store used for publishing.defaultCollectionName- Collection to serve when none is specified.publishId- Unique prefix for all keys in the KV Store. Override only for advanced setups (e.g., multiple apps sharing the same KV Store).staticPublisherWorkingDir- Directory to hold working files during publish.
const rc = {
storageMode: 'kv-store',
kvStore: {
kvStoreName: 'site-content',
},
defaultCollectionName: 'live',
publishId: 'default',
staticPublisherWorkingDir: './static-publisher',
};
export default rc;All fields are required unless specified otherwise.
storageMode- Specifies the storage mode. Set to's3'.s3.region- The region of the S3-compatible bucket.s3.bucket- The name of the S3-compatible bucket.s3.endpoint- (optional) The custom endpoint for the S3-compatible bucket.defaultCollectionName- Collection to serve when none is specified.publishId- Unique prefix for all keys in the bucket. Override only for advanced setups (e.g., multiple apps sharing the same bucket).staticPublisherWorkingDir- Directory to hold working files during publish.
const rc = {
storageMode: 's3',
s3: {
region: 'us-east-1',
bucket: 'my-site-content',
},
defaultCollectionName: 'live',
publishId: 'default',
staticPublisherWorkingDir: './static-publisher',
};
export default rc;This file is included as part of the scaffolding. Every time you publish content, the publish settings in this file are used for publishing the content, and the server settings are taken from this file and saved as the settings used by the server for that collection.
const config = {
// these paths are relative to compute-js dir
rootDir: '../public',
// Include/exclude filters (optional):
excludeDirs: ['node_modules'],
excludeDotFiles: true,
includeWellKnown: true,
// Advanced filtering (optional):
assetInclusionTest: (key, contentType) => {
return true; // include everything by default
},
// Override which compressed variants to create for each asset during publish (optional):
contentCompression: ['br', 'gzip'],
// Brotli quality, 0 to 11 (optional, default 11):
brotliQuality: 11,
// Content type definitions/overrides (optional):
contentTypes: [
{ test: /\.custom$/, contentType: 'application/x-custom', text: false },
],
// Server settings
server: {
spaFile: '/spa.html',
notFoundPageFile: '/404.html',
autoIndex: ['index.html'],
autoExt: ['.html'],
staticItems: ['/static/', '/assets/'],
allowedEncodings: ['br', 'gzip'],
}
};
export default config;You can override this file for a single publish-content command by specifying an alternative using --config on the command line.
-
rootDir- Directory to scan for content, relative to this file (required). -
excludeDirs- Array of directory names or regex patterns to exclude (default:['./node_modules']). -
excludeDotFiles- Exclude dotfiles and dot-named directories (default: true). -
includeWellKnown- Always include.well-knowneven if dotfiles are excluded (default: true). -
assetInclusionTest- Function to determine inclusion and variant behavior per file. -
contentCompression- Array of compression formats to pre-generate (['br', 'gzip']by default). -
brotliQuality- Brotli quality for thebrvariants, an integer from 0 to 11 (default: 11). Quality 11 gives the smallest files but is the slowest. Quality 10 is approximately 3 times faster, and its files are approximately 2% larger. If you change this value, variants that are already in storage are not compressed again. -
contentTypes- Additional or override content type definitions. -
server- Server runtime config that contains the following fields:publicDirPrefix- Deprecated. A future version will remove it. A prefix that the Publisher Server adds to the path of each request, to get the asset key. Use it if the files to serve are in a subdirectory ofrootDir. For example, ifrootDiris'../site'and the files to serve are in'../site/public', set it to'/public'. The value starts with/and does not end with/(default:''). The scaffolder sets it from--root-dirand--public-dir.spaFile- Path to a file to be used to serve in a SPA application.notFoundPageFile- Path to a file to be used to serve as a 404 not found page.autoIndex- List of files to automatically use as index.autoExt- List of extensions to automatically apply to a path and retry when the requested path is not found.staticItems- Directories to specify as containing 'static' files. The Publisher Server will serve files from these directories with a long TTL.allowedEncodings- Specifies which compression formats the server is allowed to serve based on the client'sAccept-Encodingheader.
Collections are a powerful feature that allow you to publish and manage multiple versions of your site simultaneously. Each collection is a named set of:
- Static files
- Server configuration (e.g., fallback file, static directories, etc.)
- An index file that maps paths to those hashes
Collections are published using a --collection-name, and can be reused, updated, or deleted independently. For example, you can create a staging version of your site using:
npx @fastly/compute-js-static-publish publish-content \
--collection-name=staging \
--expires-in=7d \
--config=./publish-content.config.jsYou can overwrite or republish any collection at any time. Old file hashes will be reused automatically where contents match.
Collections can expire automatically:
- Expired collections are ignored by the server and return 404s
- The default collection never expires
- Expiration limits can be modified (shortened, extended, reenstated) using
collections update-expiration - They are cleaned up by
clean --delete-expired-collections
--expires-in=3d # relative (e.g. 1h, 2d, 1w)
--expires-at=2025-05-01T12:00Z # absolute (ISO 8601)
--expires-never # the collection never expires(Only one of --expires-in, --expires-at, or --expires-never may be specified)
By default, the server app serves assets from the "default collection", named in static-publish.rc.js under defaultCollectionName. To switch the active collection, you add custom code to your Compute app that calls publisherServer.setActiveCollectionName(name):
publisherServer.setActiveCollectionName("preview-42");Pass null to select the default collection.
If the app turns on sandbox reuse, Compute can use a sandbox for more than one request (see Sandbox lifecycle), and the active collection stays set until you change it. Thus, call setActiveCollectionName() for each request, also when the request does not select a collection.
In the following example, assume that the Compute application is hosted using a wildcard domain *.example.com. A request for preview-pr-123.example.com would activate the collection 'pr-123'.
import { PublisherServer, collectionSelector } from '@fastly/compute-js-static-publish';
import rc from '../static-publish.rc.js';
const publisherServer = PublisherServer.fromStaticPublishRc(rc);
addEventListener("fetch", event => {
const request = event.request;
const collectionName = collectionSelector.fromHostDomain(request, /^preview-([^\.]*)\./);
publisherServer.setActiveCollectionName(collectionName); // null selects the default collection
event.respondWith(publisherServer.serveRequest(request));
});The collectionSelector module provides helpers to extract a collection name from different parts of a request:
collectionSelector.fromHostDomain(request, /^preview-([^\.]*)\./);collectionSelector.fromRequestUrl(request, url => url.pathname.split('/')[2]);collectionSelector.fromRequest(request, req => req.headers.get('x-collection') ?? 'live');See fromCookie for details on this feature.
const { collectionName, redirectResponse } = collectionSelector.fromCookie(request);collectionSelector.fromConfigStore('my-config-store', 'collection-key');If you're happy with a preview or staging collection and want to make it live, use the collections promote command:
npx @fastly/compute-js-static-publish collections promote \
--collection-name=staging
--to=liveThis copies all content and server settings from the staging collection to live.
You can also specify a new expiration:
npx @fastly/compute-js-static-publish collections promote \
--collection-name=preview-42 \
--to=staging \
--expires-in=7dNote
The collection denoted by defaultCollectionName is exempt from expiration.
During development, the local development server (npm run dev:start) runs against assets loaded into the simulated KV Store provided by the local development environment.
Prior to starting the server, publish the content to the simulated KV Store:
npm run dev:publish # 'publish' your files to the simulated local KV Store
npm run dev:start # preview locallyThis simulates publishing by writing to kvstore.json instead of uploading to the actual KV Store. You can preview your site at http://127.0.0.1:7676 - no Fastly account or service required.
Note that for local development, you will have to stop and restart the local development server each time you publish updates to your content.
To publish to an alternative collection name, use the following command and then restart the local development server:
npm run dev:publish -- --collection-name=preview-123When you're ready for production:
- Create a free Fastly account if you haven't already.
- Run
npm run fastly:deploy- This builds your Compute app into a Wasm binary
- Deploys it to a new or existing Fastly Compute service
- If creating a new service:
- you'll be prompted for backend info - you can skip this, as no backend is needed (all content is served from KV)
- KV Store will be created if necessary and automatically linked to your new service.
Once deployed, publish content like so:
npm run fastly:publishThis:
- Uses the default collection name
- Uploads static files to the KV Store
- Stores server configuration for the collection
Tip
Upload to a specific collection by specifying the collection name when publishing content:
npm run fastly:publish -- --collection-name=preview-42No Wasm redeploy needed unless you:
- Modify
src/index.js- such as when you update your custom routing logic (e.g. collection selection) or - Change
static-publish.rc.js
If you do need to rebuild and redeploy the Compute app, simply run:
npm run fastly:deployDuring development, the local development server (npm run dev:start) runs against assets published to S3-compatible storage.
In this mode, the content in the S3-compatible storage can be updated independent of the Compute application running in the local development server.
Before publishing your content, your S3-compatible bucket must already exist.
Tip
If you're using Fastly Object Storage, create your Fastly account if you haven't already, and then follow the Object Storage quick start to set up your bucket.
Publish your content by typing:
npm run s3:publishThen start the local development server:
npm run dev:startWhen you're ready for production:
- Create a free Fastly account if you haven't already.
- Run
npm run fastly:deploy- This builds your Compute app into a Wasm binary
- Deploys it to a new or existing Fastly Compute service
- If creating a new service, the Fastly CLI uses the
[setup]section offastly.toml:- It creates the backend
s3_storage. It prompts you for the hostname and the port of the backend. The default values are the hostname of your S3 endpoint and 443. Press Enter to accept them. - It creates the Secret Store
S3_CREDENTIALSand prompts you for the values ofS3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEY. Use credentials that can read the bucket. - It links the backend and the Secret Store to your new service.
- It creates the backend
- If you deploy to an existing service, the Fastly CLI does not use the
[setup]section. Add the backend and the Secret Store to the service yourself, with the same names.
Once deployed, publish content like so:
npm run s3:publishThis:
- Uses the default collection name
- Uploads static files to the S3-compatible bucket
- Stores server configuration for the collection
Tip
Upload to a specific collection by specifying the collection name when publishing content:
npm run s3:publish -- --collection-name=preview-42No Wasm redeploy needed unless you:
- Modify
src/index.js- such as when you update your custom routing logic (e.g. collection selection) or - Change
static-publish.rc.js
If you do need to rebuild and redeploy the Compute app, simply run:
npm run fastly:deployEvery time you publish, old files are left behind for safety. However, files with the same content will be re-used across collections and publishing events. They are only stored once in the KV Store using their content hash as a key. This ensures that unchanged files aren't duplicated, keeping storage efficient and deduplicated.
Over time, however, collections may expire, old versions of files will be left behind, and some assets in the KV Store will no longer be referenced by any live collection. To avoid bloat, use:
npm run dev:cleanand
npm run fastly:cleanThese scripts run against the local and Fastly KV Stores respectively, and run the following command:
npx @fastly/compute-js-static-publish clean --delete-expired-collections
This removes:
- Expired collection index files (only if
--delete-expired-collectionsis passed) - Unused content blobs (no longer referenced)
- Orphaned server config files
Preview what will be deleted without making changes:
npx @fastly/compute-js-static-publish clean --dry-run
β οΈ Cleanup never deletes the default collection and never deletes content thatβs still in use.
This project supports pre-compressing and serving assets in Brotli and Gzip formats. Compression is controlled at two different stages:
- During publishing, the
contentCompressionfield in thepublishsection ofpublish-content.config.jsdefines which compressed variants (e.g.,br,gzip) should be generated and uploaded to storage.
Assets are stored in multiple formats (uncompressed + compressed) if configured. A compressed variant is uploaded and used only if it is smaller than the original. (For very small files, the compressed variant is often larger.) If a variant is already in storage, it is not compressed or uploaded again. Files are compressed in parallel on the Node.js thread pool. By default, the CLI sets UV_THREADPOOL_SIZE to the number of CPU cores (minimum 4, maximum 16). To use a different value, set UV_THREADPOOL_SIZE yourself. To make compression faster, you can also set a lower brotliQuality. The following file types are compressed by default:
-
Text-based:
.html,.js,.css,.svg,.json,.txt,.xml,.map -
Certain binary formats:
.bmp,.tar -
At runtime, the
allowedEncodingsfield in theserversection ofpublish-content.config.jsspecifies which compression formats the server is allowed to serve based on the client'sAccept-Encodingheader.
PublisherServer will serve the smallest appropriate version based on the Accept-Encoding header.
You can combine PublisherServer with custom logic to support APIs, authentication, redirects, or A/B testing. PublisherServer returns null when it cannot handle a request, allowing you to chain in your own logic.
import { PublisherServer } from '@fastly/compute-js-static-publish';
import rc from '../static-publish.rc.js';
const publisherServer = PublisherServer.fromStaticPublishRc(rc);
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const response = await publisherServer.serveRequest(request);
if (response) {
return response;
}
// Add your custom logic here
if (request.url.endsWith('/api/hello')) {
return new Response(JSON.stringify({ greeting: "hi" }), {
headers: { 'content-type': 'application/json' }
});
}
return new Response("Not Found", { status: 404 });
}PublisherServer can report how long it spends reading from storage. To turn this on, give it the name of a request header:
publisherServer.setServerTimingRequestHeader('X-Publisher-Server-Timing');A request that has this header gets a Server-Timing response header. Requests without it are not affected. This is off by default.
Server-Timing: settings;dur=12.3, index;dur=40.1, index-body;dur=31.0, index-parse;dur=24.2, asset;dur=11.4
| Name | Duration of |
|---|---|
settings |
Reading and parsing the collection's settings |
index |
Reading the collection's index, until the response headers arrive |
index-body |
Reading the body of the index |
index-parse |
Parsing the index |
asset |
Reading the file, until the response headers arrive. Can appear more than one time, one time for each variant that is tried. |
cache |
Looking up the response cache, if it is on (see Caching Responses with the Core Cache). desc is hit (with the age of the cached response, for example hit age=42s), miss, or bypass. |
An entry is missing if PublisherServer did not read that item for this request, for example because the item is cached in memory, or because the response came from the response cache.
serveRequest() starts the measurements for each request. If you call getMatchingAsset() and serveAsset() directly, call beginRequest() first:
publisherServer.beginRequest(request);
const asset = await publisherServer.getMatchingAsset(pathname);
if (asset != null) {
return publisherServer.serveAsset(request, asset);
}Any client can send the request header. Choose a name that is not easy to guess if you do not want to show these timings to the public.
To serve a file, PublisherServer reads the collection's settings, the collection's index, and the file. Fastly can cache these reads, but PublisherServer must still parse the index for each request, and the index of a large site is large. To skip this work, PublisherServer can cache whole responses with the Fastly Core Cache API:
publisherServer.setResponseCache({ maxAge: 3600 });maxAge is in seconds. This is off by default.
maxBodySize sets the largest response, in bytes, to cache. The default is 8 MiB (8 * 1024 * 1024). A larger response is served from storage each time:
publisherServer.setResponseCache({ maxAge: 3600, maxBodySize: 16 * 1024 * 1024 });- A response is cached by the publish ID, the collection, the path, and the client's
Accept-Encoding. A cache hit does not read the settings, the index, or the file. - A path that does not match a file is cached, too, so a missing path does not read the index again.
- If several requests miss the same response at the same time, one request produces it, and the others wait for it.
- Only
200and404responses are cached. Conditional requests (304) andHEADrequests are answered from the cached response. - Each cached response has the surrogate key
<publishId>-<collectionName>. After you publish,publish-contentpurges this key (see--fastly-service-id), so the next request gets the new content.
serveRequest() uses the response cache automatically. If you call getMatchingAsset() and serveAsset() directly, wrap them in serveCached(). When a response is not cached yet, serveCached() calls your function to produce it, with a GET request that has no conditional headers:
publisherServer.beginRequest(request);
const response = await publisherServer.serveCached(request, pathname, async (fillRequest) => {
const asset = await publisherServer.getMatchingAsset(pathname);
if (asset == null) {
return null; // not found; this result is cached, too
}
return publisherServer.serveAsset(fillRequest, asset);
});The second argument identifies the response in the collection. If anything other than the path and Accept-Encoding changes your response, include it in this value.
Notes:
- A response is read completely before it is cached, so a request uses memory for one response, up to
maxBodySize. Other requests that wait for the same response get it when it is cached. - A response is cached only if its body has the size in the file's metadata. If storage returns a different body, nothing is cached, the request gets an error, and the next request reads the file again.
- With
--local,publish-contentcannot purge the local cache. Restartfastly compute serveto see newly published content, or leave the response cache off in local development. - On staging, a purge must include the
Fastly-Purge-Environment: stagingheader to clear the staging cache. Without it, a purge clears only the production cache. Usepublish-content --purge-environment=production,stagingto purge both.
To access files you've published, use the getMatchingAsset() and loadAssetVariant() methods on publisherServer.
const asset = await publisherServer.getMatchingAsset('/index.html');
if (asset != null) {
// Asset exists with that path
asset.contentType; // e.g., 'text/html'
asset.lastModifiedTime; // Unix timestamp
asset.size; // Size in bytes of the base version
asset.hash; // SHA256 hash of base version
asset.variants; // Available variants (e.g., ['gzip'])
}const assetVariant = await publisherServer.loadAssetVariant(asset, null); // pass 'gzip' or 'br' for compressed
assetVariant.storageEntry; // Storage entry (type StorageEntry defined in './src/storage/storage-provider.ts')
assetVariant.size; // Size of the variant
assetVariant.hash; // SHA256 of the variant
assetVariant.contentEncoding; // 'gzip', 'br', or null
assetVariant.numChunks; // Number of chunks (for large files)You can stream assetVariant.storageEntry.body directly to a Response, or read it using .text(), .json(), or .arrayBuffer() depending on its content type.
npx @fastly/compute-js-static-publish@latest [options]- Scaffold a new Compute app
publish-content- Publish static files to storage (KV Store or S3-compatible storage) under a named collectionclean- Delete expired and unreferenced entries in storagecollections list- List all published collectionscollections delete- Delete a specific collection indexcollections promote- Copy a collection to another namecollections update-expiration- Modify expiration time for an existing collection
Run outside an existing Compute app directory:
# Using KV store storage
npx @fastly/compute-js-static-publish@latest \
--root-dir=./public \
--kv-store-name=<site-content>
# Using S3 storage (BETA)
npx @fastly/compute-js-static-publish@latest \
--root-dir=./public \
--storage-mode=s3 \
--s3-region=<s3 region> \
--s3-bucket=<bucket name> \
[--s3-endpoint=<endpoint>]npx @fastly/compute-js-static-publish@latest \
--root-dir=./public \
{ [--storage-mode=kv-store] --kv-store-name=<site-content> | \
--storage-mode=s3 --s3-region=<s3 region> --s3-bucket=<bucket name> [--s3-endpoint=<endpoint>] } \
[--output=./compute-js] \
[--static-publisher-working-dir=<output>/static-publisher] \
[--publish-id=<prefix>] \
[--static-dir=./public/static] \
[--spa=./public/spa.html] \
[--not-found-page=./public/404.html] \
[--auto-index=index.html,index.htm] \
[--auto-ext=.html,.htm] \
[--name=my-site] \
[--description='My Compute static site'] \
[--author='you@example.com'] \
[--service-id=your-fastly-service-id]Used to generate the Compute app:
-
--storage-mode: Specifies the storage mode. Must be eitherkv-storeors3(default:kv-store).If
--storage-mode=kv-store:--kv-store-name: Required. Name of KV Store to use.
If
--storage-mode=s3:--s3-region: Required. Region of the S3-compatible bucket.--s3-bucket: Required. Name of the S3-compatible bucket.--s3-endpoint: Optional. Custom endpoint of the S3-compatible bucket, if necessary.
-
--output: Compute app destination (default:./compute-js) -
--static-publisher-working-dir: Directory to hold working files (default:<output>/static-publisher). -
--name: Application name to insert intofastly.toml -
--description: Application description to insert intofastly.toml -
--author: Author to insert intofastly.toml -
--service-id: Optional existing Fastly Compute service ID
Used in building config files:
--root-dir: Required. Directory of static site content.--publish-id: Optional key prefix for KV entries (default:'default').--public-dir: Deprecated. A future version will remove it. Public files base directory, under--root-dir(default: same as--root-dir). Files that are under--root-dirbut not under--public-dirare published, but requests cannot get them.--static-dir: One or more directories to serve with long cache TTLs.--spa: SPA fallback file path (e.g.,./public/spa.html).--not-found-page: 404 fallback file path (e.g.,./public/404.html).--auto-index: List of filenames to use as index files.--auto-ext: Extensions to try when resolving URLs.
Once you're in the scaffolded Compute app directory (with static-publish.rc.js present), you can run these subcommands:
npx @fastly/compute-js-static-publish publish-content \
[--root-dir=./public] \
[--collection-name=preview-42] \
[--config=./publish-content.config.js] \
[--expires-in=7d | --expires-at=2025-05-01T12:00Z | --expires-never] \
[--overwrite-existing] \
[--brotli-quality=<0-11>] \
[--local] \
[--fastly-api-token=...] \
[--fastly-service-id=...] \
[--purge-environment=production,staging] \
[--s3-access-key-id=... --s3-secret-access-key=...] \
[--verbose]Publishes static files from your local root directory into a named collection, in the storage that static-publish.rc.js specifies: the Fastly KV Store, S3-compatible storage (BETA), or a local dev directory (--local, KV Store mode only).
Before it scans your files, the command lists the files that are already in storage. Files that already exist with the same hash are not compressed or uploaded again. With the KV Store, the command uploads the remaining files in batches.
After this process is complete, the PublisherServer object in the Compute application will see the updated index of files and updated server settings from the publish-content.config.js file.
-
--collection-name: Name of the collection to create/update (default: value instatic-publish.rc.js) -
--config: Path to a config file to configure server behavior for this collection (default:./publish-content.config.js) -
--root-dir: Source directory to read files from (overrides value inpublish-content.config.js) -
--overwrite-existing: Always upload all files, even if they are already in storage.--kv-overwriteis an alias. -
--brotli-quality: Brotli quality for thebrvariants, an integer from 0 to 11. OverridesbrotliQualityinpublish-content.config.js. This is useful in CI, where the config file is generated at each run. -
--fastly-service-id: The Fastly Service ID to purge after publishing. The command purges the surrogate key<publishId>-<collectionName>, so that the service stops serving cached copies of the collection's settings, index, files, and responses (see response cache). If not set, the tool will check:FASTLY_SERVICE_IDenvironment variableservice_idinfastly.toml
If none is found, the command skips the purge. The purge also needs an API token (see
--fastly-api-token). With--local, there is no purge. If the purge fails, the command shows a warning and completes, because the content is already published. -
--purge-environment: The environments to purge:production(the default),staging, or both, as a comma-separated list (--purge-environment=production,staging). You can also repeat the option. Purgestagingif you test the collection on a staged service version, because a purge without it does not clear the staging cache. -
--verbose: Verbose mode. When enabled, the command logs a line for each file that the command scans, and for each upload. Without this option, the command logs the progress after each 1,000 files or uploads.
Expiration:
--expires-in: Time-to-live from now (e.g.1h,2d,1w)--expires-at: Absolute expiration time (ISO format:2025-05-01T12:00Z)--expires-never: Collection never expires
At most one of --expires-in, --expires-at, or --expires-never may be specified
KV Store Options:
-
--local: Instead of working with the Fastly KV Store, operate on local files that will be used to simulate the KV Store with the local development environment. KV Store mode only. In S3 mode,--localis an error, because there is no local simulation of S3 storage. -
--fastly-api-token: API token to use when publishing. If not set, the tool uses theFASTLY_API_TOKENenvironment variable.
S3 Storage Options (BETA):
--s3-access-key-id,--s3-secret-access-key: Access key ID and secret access key for S3-compatible storage. If not set, the tool will check theS3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEYenvironment variables.--s3-upload-concurrency: Number of objects to upload at the same time, from 1 to 256 (default: 64). Each upload keeps its file in memory, so a site with many large files can need a lower value.
npx @fastly/compute-js-static-publish clean \
[--delete-expired-collections] \
[--dry-run]Cleans up expired or unreferenced items in storage (KV Store or S3-compatible storage). This can include expired collection indexes and orphaned content assets.
--delete-expired-collections: If set, expired collection index files will be deleted.--dry-run: Show what would be deleted without performing any deletions.
KV Store Options:
-
--local: Instead of working with the Fastly KV Store, operate on local files that will be used to simulate the KV Store with the local development environment. KV Store mode only. In S3 mode,--localis an error, because there is no local simulation of S3 storage. -
--fastly-api-token: API token to use when publishing. If not set, the tool uses theFASTLY_API_TOKENenvironment variable.
S3 Storage Options (BETA):
--s3-access-key-id,--s3-secret-access-key: Access key ID and secret access key for S3-compatible storage. If not set, the tool will check theS3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEYenvironment variables.
npx @fastly/compute-js-static-publish collections listLists all collections currently published in storage.
KV Store Options:
-
--local: Instead of working with the Fastly KV Store, operate on local files that will be used to simulate the KV Store with the local development environment. KV Store mode only. In S3 mode,--localis an error, because there is no local simulation of S3 storage. -
--fastly-api-token: API token to use when publishing. If not set, the tool uses theFASTLY_API_TOKENenvironment variable.
S3 Storage Options (BETA):
--s3-access-key-id,--s3-secret-access-key: Access key ID and secret access key for S3-compatible storage. If not set, the tool will check theS3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEYenvironment variables.
npx @fastly/compute-js-static-publish collections promote \
--collection-name=preview-42 \
--to=live \
[--expires-in=7d | --expires-at=2025-06-01T00:00Z | --expires-never]Copies an existing collection (content + config) to a new collection name.
--collection-name: The name of the source collection to promote (required)--to: The name of the new (target) collection to create or overwrite (required)
Expiration:
--expires-in: Time-to-live from now (e.g.1h,2d,1w)--expires-at: Absolute expiration time (ISO format)--expires-never: Collection never expires
At most one of --expires-in, --expires-at, or --expires-never may be specified. If not provided, then the existing expiration rule of the collection being promoted is used.
KV Store Options:
-
--local: Instead of working with the Fastly KV Store, operate on local files that will be used to simulate the KV Store with the local development environment. KV Store mode only. In S3 mode,--localis an error, because there is no local simulation of S3 storage. -
--fastly-api-token: API token to use when publishing. If not set, the tool uses theFASTLY_API_TOKENenvironment variable.
S3 Storage Options (BETA):
--s3-access-key-id,--s3-secret-access-key: Access key ID and secret access key for S3-compatible storage. If not set, the tool will check theS3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEYenvironment variables.
npx @fastly/compute-js-static-publish collections update-expiration \
--collection-name=preview-42 \
--expires-in=3d | --expires-at=2025-06-01T00:00Z | --expires-neverSets or updates the expiration time of an existing collection.
--collection-name: The name of the collection to update (required)
Expiration:
--expires-in: Time-to-live from now (e.g.1h,2d,1w)--expires-at: Absolute expiration time (ISO format)--expires-never: Collection never expires
Exactly one of --expires-in, --expires-at, or --expires-never must be specified
KV Store Options:
-
--local: Instead of working with the Fastly KV Store, operate on local files that will be used to simulate the KV Store with the local development environment. KV Store mode only. In S3 mode,--localis an error, because there is no local simulation of S3 storage. -
--fastly-api-token: API token to use when publishing. If not set, the tool uses theFASTLY_API_TOKENenvironment variable.
S3 Storage Options (BETA):
--s3-access-key-id,--s3-secret-access-key: Access key ID and secret access key for S3-compatible storage. If not set, the tool will check theS3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEYenvironment variables.
npx @fastly/compute-js-static-publish collections delete \
--collection-name=preview-42Deletes a collection index from storage. The content files will remain as they may still be referenced by other collection indexes.
Use the npx @fastly/compute-js-static-publish clean command afterward to remove content files that are no longer referenced by any collection.
If a Service ID is found, the command then purges the surrogate key <publishId>-<collectionName>, so that the service stops serving cached responses of the deleted collection. This uses the same options as publish-content.
-
--collection-name: The name of the collection to delete (required) -
--fastly-service-id: The Fastly Service ID to purge after deleting. If not set, the tool checks theFASTLY_SERVICE_IDenvironment variable, thenservice_idinfastly.toml. If none is found, the command skips the purge. The purge also needs an API token (see--fastly-api-token). With--local, there is no purge. If the purge fails, the command shows a warning and completes, because the collection is already deleted. -
--purge-environment: The environments to purge:production(the default),staging, or both, as a comma-separated list. You can also repeat the option.
KV Store Options:
-
--local: Instead of working with the Fastly KV Store, operate on local files that will be used to simulate the KV Store with the local development environment. KV Store mode only. In S3 mode,--localis an error, because there is no local simulation of S3 storage. -
--fastly-api-token: API token to use when publishing. If not set, the tool uses theFASTLY_API_TOKENenvironment variable.
S3 Storage Options (BETA):
--s3-access-key-id,--s3-secret-access-key: Access key ID and secret access key for S3-compatible storage. If not set, the tool will check theS3_ACCESS_KEY_IDandS3_SECRET_ACCESS_KEYenvironment variables.
collectionSelector.fromCookie is a utility function that enables the use of a browser
cookie to select the active collection.
import { collectionSelector } from '@fastly/compute-js-static-publish';
const { collectionName, redirectResponse } =
collectionSelector.fromCookie(request, {
// Everything here is optional β these are just examples
cookieName: 'publisher-collection', // default
activatePath: '/activate', // default
resetPath: '/reset', // default
cookieHttpOnly: true, // default
cookieMaxAge: 60 * 60 * 24 * 7, // 7-days. default is `null`, never expires.
cookiePath: '/', // default
});
if (redirectResponse) {
// honor the redirect
return redirectResponse;
}
// `collectionName` now holds the active collection (or `null` if none)-
Reads a cookie
It looks for a cookie named cookieName (default
publisher-collection) and hands you the value ascollectionName. -
Handles two helper endpoints for you
Endpoint Purpose Query params Result activatePath (default /activate)Set the cookie collection(required) - name of the collection
redirectTo(optional,/by default)302redirect with aSet-CookieheaderresetPath (default /reset)Clear the cookie redirectTo(optional,/by default)302redirect that expires the cookieIf a visitor accesses
/activate?collection=blue&redirectTo=/preview, the helper will issue a redirect and droppublisher-collection=blueinto their cookie jar.- If someone forgets
?collection=?? Then/activatereplies with HTTP400.
When the visitor hits
/reset, the cookie is deleted. - If someone forgets
-
Safety flags are baked in
HttpOnlyis on by default (configurable), to help avoid XSS issues.Secureis automatically added on HTTPS requests.SameSite=Laxis always set β reasonable default for previews.
| Option | Type | Default | Notes |
|---|---|---|---|
cookieName |
string | 'publisher-collection' |
Name of the cookie to read/write |
activatePath |
string | '/activate' |
Path that sets the cookie |
resetPath |
string | '/reset' |
Path that clears the cookie |
cookieHttpOnly |
boolean | true |
Turn off if the client-side needs to read it |
cookieMaxAge |
number|undefined | undefined |
Seconds until expiry; omit for session cookies |
cookiePath |
string | '/' |
Path attribute in the cookie |
fromCookie can be dropped into a Fastly Compute app:
async function handleRequest(event) {
const request = event.request;
// --- Cookie handling starts here
const { collectionName, redirectResponse } = collectionSelector.fromCookie(request);
if (redirectResponse) {
// obey redirect first
return redirectResponse;
}
publisherServer.setActiveCollectionName(collectionName); // null selects the default collection
// --- Cookie handling ends here
// Regular routing follows...
const response = await publisherServer.serveRequest(request);
if (response != null) {
return response;
}
return new Response('Not found', { status: 404 });
}- View CLI command help:
npx @fastly/compute-js-static-publish --help - Use in CI to automate branch previews
- Visit https://developer.fastly.com for Compute platform docs
To build the package, run npm run build. To run the tests, see test/README.md.
If you encounter any non-security-related bug or unexpected behavior, please file an issue using the bug report template.
Please see our SECURITY.md for guidance on reporting security-related issues.
MIT.