Skip to content

About

Static Publisher for Fastly Compute JavaScript

Topics

Resources

Code of conduct

Security policy

Stars

50 stars

Watchers

10 watching

Forks

Repository files navigation

Static Publisher for JavaScript on Fastly Compute

NOTE: @fastly/compute-js-static-publish is 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.

πŸ“– Table of Contents

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:

✨ Key Features

  • πŸ“¦ 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

🏁 Quick Start

πŸ“¦ Using the Fastly KV Store

1. Scaffold a Compute App

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-content

You 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 install

2. Preview Locally

Type the following β€” no Fastly account or service required yet!

npm run dev:publish
npm run dev:start

Fastly's local development environment serves your static website at http://127.0.0.1:7676, powered by a simulated KV Store.

3. Deploy Your App

Ready to go live? All you need is a free Fastly account!

npm run fastly:deploy

The command publishes your Compute app and creates the KV Store. (No content uploaded yet!)

4. Publish Content

npm run fastly:publish

This command uploads your static files to the KV Store and applies the server config. Your website is now up and live!

🎁 Using S3 Compatible Storage (Beta)

1. Scaffold a Compute App

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.app

You 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 install

2. Publish your content to S3-compatible storage

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.

Type the following:

npm run s3:publish

3. Preview Locally

Type the following to preview your site locally:

npm run dev:start

Fastly's local development environment serves your static website at http://127.0.0.1:7676. Your content is fetched from S3-compatible storage.

4. Deploy Your App

Ready to go live? All you need is a free Fastly account!

npm run fastly:deploy

The 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.


πŸ—‚ Project Layout

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

βš™οΈ Configuring static-publish.rc.js

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.

Using the Fastly KV Store

Fields:

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.

Example: static-publish.rc.js

const rc = {
  storageMode: 'kv-store',
  kvStore: {
    kvStoreName: 'site-content',
  },
  defaultCollectionName: 'live',
  publishId: 'default',
  staticPublisherWorkingDir: './static-publisher',
};

export default rc;

Using S3-compatible storage (BETA)

Fields:

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.

Example: static-publish.rc.js

const rc = {
  storageMode: 's3',
  s3: {
    region: 'us-east-1',
    bucket: 'my-site-content',
  },
  defaultCollectionName: 'live',
  publishId: 'default',
  staticPublisherWorkingDir: './static-publisher',
};

export default rc;

🧾 Config for Publishing and Server: publish-content.config.js

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.

Fields:

  • 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-known even 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 the br variants, 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 of rootDir. For example, if rootDir is '../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-dir and --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's Accept-Encoding header.

πŸ“¦ Collections (Publish, Preview, Promote)

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.js

You can overwrite or republish any collection at any time. Old file hashes will be reused automatically where contents match.

Expiration (Auto-cleanup)

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)

Switching the active collection

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.

Example: Subdomain-based Routing

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));
});

πŸ”€ Selecting a Collection at Runtime

The collectionSelector module provides helpers to extract a collection name from different parts of a request:

collectionSelector.fromHostDomain(request, /^preview-([^\.]*)\./);

From the Request URL

collectionSelector.fromRequestUrl(request, url => url.pathname.split('/')[2]);

With a Custom Request Matcher

collectionSelector.fromRequest(request, req => req.headers.get('x-collection') ?? 'live');

From a Cookie

See fromCookie for details on this feature.

const { collectionName, redirectResponse } = collectionSelector.fromCookie(request);

From a Fastly Config Store

collectionSelector.fromConfigStore('my-config-store', 'collection-key');

πŸš€ Promoting a Collection

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=live

This 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=7d

Note

The collection denoted by defaultCollectionName is exempt from expiration.

πŸ›  Development β†’ Production Workflow

Using the KV Store

Local development

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 locally

This 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-123

Production

When you're ready for production:

  1. Create a free Fastly account if you haven't already.
  2. 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:publish

This:

  • 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-42

No 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:deploy

Using S3-compatible storage (BETA)

Local development

During 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:publish

Then start the local development server:

npm run dev:start

Production

When you're ready for production:

  1. Create a free Fastly account if you haven't already.
  2. 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 of fastly.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_CREDENTIALS and prompts you for the values of S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY. Use credentials that can read the bucket.
      • It links the backend and the Secret Store to your new service.
    • 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:publish

This:

  • 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-42

No 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:deploy

🧹 Cleaning Up

Every 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:clean

and

npm run fastly:clean

These 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-collections is passed)
  • Unused content blobs (no longer referenced)
  • Orphaned server config files

πŸ” Dry Run Mode

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.

πŸ”„ Content Compression

This project supports pre-compressing and serving assets in Brotli and Gzip formats. Compression is controlled at two different stages:

  • During publishing, the contentCompression field in the publish section of publish-content.config.js defines 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 allowedEncodings field in the server section of publish-content.config.js specifies which compression formats the server is allowed to serve based on the client's Accept-Encoding header.

PublisherServer will serve the smallest appropriate version based on the Accept-Encoding header.

🧩 Using PublisherServer in Custom Apps

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 });
}

⏱️ Measuring Performance with Server-Timing

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.

πŸ—„οΈ Caching Responses with the Core Cache

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 200 and 404 responses are cached. Conditional requests (304) and HEAD requests are answered from the cached response.
  • Each cached response has the surrogate key <publishId>-<collectionName>. After you publish, publish-content purges 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-content cannot purge the local cache. Restart fastly compute serve to see newly published content, or leave the response cache off in local development.
  • On staging, a purge must include the Fastly-Purge-Environment: staging header to clear the staging cache. Without it, a purge clears only the production cache. Use publish-content --purge-environment=production,staging to purge both.

πŸ“₯ Using Published Assets in Your Code

To access files you've published, use the getMatchingAsset() and loadAssetVariant() methods on publisherServer.

Access Metadata for a File:

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'])
}

Load the File from KV Store:

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.


πŸ“š CLI Reference

Available Commands

Outside a Compute App Directory

  • npx @fastly/compute-js-static-publish@latest [options] - Scaffold a new Compute app

Inside a Compute App Directory

  • publish-content - Publish static files to storage (KV Store or S3-compatible storage) under a named collection
  • clean - Delete expired and unreferenced entries in storage
  • collections list - List all published collections
  • collections delete - Delete a specific collection index
  • collections promote - Copy a collection to another name
  • collections update-expiration - Modify expiration time for an existing collection

πŸ›  App Scaffolding

Run outside an existing Compute app directory:

Minimum required options

# 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>]

Full options list

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]

Options:

Used to generate the Compute app:

  • --storage-mode: Specifies the storage mode. Must be either kv-store or s3 (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 into fastly.toml

  • --description: Application description to insert into fastly.toml

  • --author: Author to insert into fastly.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-dir but not under --public-dir are 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.

πŸš€ Inside a Compute App Directory

Once you're in the scaffolded Compute app directory (with static-publish.rc.js present), you can run these subcommands:

publish-content

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.

Options:
  • --collection-name: Name of the collection to create/update (default: value in static-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 in publish-content.config.js)

  • --overwrite-existing: Always upload all files, even if they are already in storage. --kv-overwrite is an alias.

  • --brotli-quality: Brotli quality for the br variants, an integer from 0 to 11. Overrides brotliQuality in publish-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_ID environment variable
    • service_id in fastly.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. Purge staging if 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, --local is 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 the FASTLY_API_TOKEN environment 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 the S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY environment 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.

clean

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.

Options:
  • --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, --local is 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 the FASTLY_API_TOKEN environment 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 the S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY environment variables.

collections list

npx @fastly/compute-js-static-publish collections list

Lists all collections currently published in storage.

Options:

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, --local is 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 the FASTLY_API_TOKEN environment 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 the S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY environment variables.

collections promote

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.

Options:
  • --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, --local is 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 the FASTLY_API_TOKEN environment 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 the S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY environment variables.

collections update-expiration

npx @fastly/compute-js-static-publish collections update-expiration \
  --collection-name=preview-42 \
  --expires-in=3d | --expires-at=2025-06-01T00:00Z | --expires-never

Sets or updates the expiration time of an existing collection.

Options:
  • --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, --local is 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 the FASTLY_API_TOKEN environment 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 the S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY environment variables.

collections delete

npx @fastly/compute-js-static-publish collections delete \
  --collection-name=preview-42

Deletes 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.

Options:
  • --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 the FASTLY_SERVICE_ID environment variable, then service_id in fastly.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, --local is 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 the FASTLY_API_TOKEN environment 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 the S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY environment variables.

πŸ“• Appendix

fromCookie

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)

What it does, in plain English

  1. Reads a cookie

    It looks for a cookie named cookieName (default publisher-collection) and hands you the value as collectionName.

  2. 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)
    302 redirect with a Set-Cookie header
    resetPath (default /reset) Clear the cookie redirectTo (optional, / by default) 302 redirect that expires the cookie

    If a visitor accesses /activate?collection=blue&redirectTo=/preview, the helper will issue a redirect and drop publisher-collection=blue into their cookie jar.

    • If someone forgets ?collection=?? Then /activate replies with HTTP 400.

    When the visitor hits /reset, the cookie is deleted.

  3. Safety flags are baked in

    • HttpOnly is on by default (configurable), to help avoid XSS issues.
    • Secure is automatically added on HTTPS requests.
    • SameSite=Lax is always set – reasonable default for previews.

Option reference

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

Example

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 });
}

πŸ“š Next Steps

  • 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

Development

To build the package, run npm run build. To run the tests, see test/README.md.

Issues

If you encounter any non-security-related bug or unexpected behavior, please file an issue using the bug report template.

Security issues

Please see our SECURITY.md for guidance on reporting security-related issues.

License

MIT.

About

Static Publisher for Fastly Compute JavaScript

Topics

Resources

Code of conduct

Security policy

Stars

50 stars

Watchers

10 watching

Forks

Releases

Used by

Contributors

Languages