A framework-aware deployment tool for static frontends. It reads your project's build configuration (e.g. angular.json, astro.config.mjs), runs the build with the configuration you choose, and pushes the compiled output to an S3 bucket — with correct content types and cache headers — so a single command takes you from source to a live, hosted site.
A framework-aware deployment tool for static frontends. It reads your project's build configuration (e.g. angular.json, astro.config.mjs), runs the build with the configuration you choose, and pushes the compiled output to an S3 bucket — with correct content types and cache headers — so a single command takes you from source to a live, hosted site.
- S3 Bucket support: Upload your frontend files to an S3 bucket.
- S3 Bucket host agnostic: Choose the host you want (Amazon, OVH, etc.).
- Angular support: It reads your
angular.jsonto know how to build and which folder is the output build folder. - Astro support: It reads your
astro.config.*to know how to build and which folder is the output build folder. - Any Front support: Adaptable to any frontend by using the
customconfig.
Requires Node.js ^20.19.0 || >=22.12.0.
bun add @czfabrics/front-ready@0.1.0-beta.9yarn add @czfabrics/front-ready@0.1.0-beta.9npm install @czfabrics/front-ready@0.1.0-beta.9Create a frontready.config.ts file at the root of your project:
import { Config } from '@czfabrics/front-ready'
export default {
bucket: {
namePrefix: 'my-front-hosting',
params: {
region: 'eu-west-3',
apiVersion: '2006-03-01',
endpoint: 'https://s3.eu-west-3.amazonaws.com',
credentials: {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
},
},
},
front: {
type: 'custom',
custom: {
build: {
command: 'npx ng build',
args: ['--configuration', 'production'],
},
environmentName: 'production',
buildOutputPath: './example',
},
},
} satisfies ConfigTip: Don't commit real credentials. Either read them from the environment — a
.envfile next to the config is loaded intoprocess.envfirst — or leavecredentialsout entirely, and the AWS SDK's default credential chain (environment variables, shared config files, an instance or IRSA role) supplies them.endpointandapiVersionare optional too: omitendpointfor AWS S3.
1. Create the bucket
npx @czfabrics/front-ready create2. Build and deploy your frontend
npx @czfabrics/front-ready deployCreate a frontready.config.ts file at the root of your project. The shape of the front key depends on how your frontend is built.
Use type: 'custom' when you want to define the build command yourself.
import { Config } from '@czfabrics/front-ready'
export default {
bucket: {
namePrefix: 'my-front-hosting',
params: {
region: 'eu-west-3',
apiVersion: '2006-03-01',
endpoint: 'https://s3.eu-west-3.amazonaws.com',
credentials: {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
},
},
},
front: {
type: 'custom',
custom: {
build: {
command: 'npx ng build',
args: ['--configuration', 'production'],
},
environmentName: 'production',
buildOutputPath: './example',
},
},
} satisfies ConfigUse type: 'angular' to let frontready read your build settings straight from angular.json.
import { Config } from '@czfabrics/front-ready'
export default {
bucket: {
namePrefix: 'my-front-hosting',
params: {
region: 'eu-west-3',
apiVersion: '2006-03-01',
endpoint: 'https://s3.eu-west-3.amazonaws.com',
credentials: {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
},
},
},
front: {
type: 'angular',
angular: {
angularJsonPath: './angular.json',
projectName: 'MyFront',
configurationName: 'production',
},
},
} satisfies ConfigAngular 6 and later. Angular 6 is the release that replaced .angular-cli.json with the
angular.json this reads; earlier workspaces are not understood.
| Workspace feature | Angular |
|---|---|
outputPath as a string |
6 and later |
outputHashing (none / media / bundles / all) |
6 and later |
:application builder, with its implicit browser/ subfolder |
17 and later |
outputPath as an object ({ base, browser }) |
17.1 and later |
Both spellings of the targets key are accepted: architect (the Angular CLI's own) and its
targets alias, so Nx-style workspaces are read too. Keys frontready doesn't need are ignored, so
a target carrying extra builder options is fine.
Use type: 'astro' to let frontready read your build settings straight from astro.config.*. It
resolves the output directory from outDir (default ./dist), resolved against root the way
Astro itself does, falling back to build.client inside it when output isn't 'static' — so you
don't repeat the path in two places.
mode is the Astro (Vite) mode: it is passed to the build as
astro build --mode <mode> and names the bucket, the way configurationName does for Angular.
import { Config } from '@czfabrics/front-ready'
export default {
bucket: {
namePrefix: 'my-front-hosting',
params: {
region: 'eu-west-3',
apiVersion: '2006-03-01',
endpoint: 'https://s3.eu-west-3.amazonaws.com',
credentials: {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
},
},
},
front: {
type: 'astro',
astro: {
mode: 'production',
},
},
} satisfies ConfigastroConfigPath is optional — astro.config.{mjs,js,ts,mts,cjs,cts} is discovered automatically at the
root of your project, and loaded through Vite exactly as Astro loads it — so a TypeScript config may
import defineConfig from astro/config, local modules, or files with a Vite suffix such as
?raw. Set it when your config lives elsewhere, and it is forwarded to the build as
--config:
front: {
type: 'astro',
astro: {
astroConfigPath: './config/astro.config.ts',
mode: 'staging',
},
},Astro 5 and later. The build is run as astro build --mode <mode>, and the --mode flag was
added in Astro 5.0 — on Astro 4 and earlier the build fails on the unknown flag.
| Config option | Astro |
|---|---|
outDir |
all supported versions |
root |
all supported versions |
output: 'static' / 'server' |
all supported versions |
build.client |
all supported versions |
output: 'hybrid' |
removed in Astro 5; still read as a server output |
Options frontready doesn't need — site, integrations, vite, and the rest — are ignored, so
the config doesn't have to be trimmed down.
Each environment gets its own bucket, named <namePrefix>-<identifier> where the identifier is
the Angular configurationName, the Astro mode or the custom environmentName. It is normalized into a valid S3 bucket name:
- lowercased, with accents stripped (
é→e); - every run of characters other than letters, digits and hyphens (
_,., spaces, …) replaced by a single-, and hyphens trimmed from both ends; - kept within S3's 63 characters by cutting the name prefix, never the identifier, so each environment keeps its own bucket — with a warning when it happens. An identifier too long to leave room for any prefix is rejected.
With namePrefix: 'my-front-hosting', the Angular configuration Prod_EU deploys to
my-front-hosting-prod-eu. Only the bucket name is affected: the build still receives the
identifier as you wrote it. An identifier with no letter or digit at all (___) is rejected before
any S3 call.
When normalization changes the name, the CLI says so, and every command shows the bucket it works
on (check and create in their result, deploy in its confirmation prompt).
Set front.prebuild to run a command before the build — code generation, writing an env file,
installing dependencies… It is available to every front type:
front: {
type: 'astro',
prebuild: {
command: 'npm',
args: ['run', 'codegen'], // optional, defaults to []
},
astro: {
mode: 'production',
},
},Only deploy runs it, after checking the bucket exists and right before the build, in the current
working directory. A non-zero exit aborts the deploy before anything is built or uploaded.
Tip: Don't commit real credentials. Either read them from the environment — a
.envfile next to the config is loaded intoprocess.envfirst — or leavecredentialsout entirely, and the AWS SDK's default credential chain (environment variables, shared config files, an instance or IRSA role) supplies them.endpointandapiVersionare optional too: omitendpointfor AWS S3.
The built-in rules depend on front.type, because each build tool names its files differently. A content-hashed file has a name that changes whenever its contents change, so it can be cached for a year and marked immutable. Every other file, and every HTML page, has to stay fresh.
Each object key is tested against the cacheControlMapping patterns in order, and the first match wins. A key that matches nothing falls back to defaultCacheControlValue, which is max-age=60, stale-while-revalidate=600, stale-if-error=86400 for every type. In the tables below, every value also carries stale-while-revalidate=600, stale-if-error=86400.
angular: everything outside assets/ and translate/ is a hashed bundle.
| Pattern | Cache-Control |
|---|---|
^index\.html$ |
max-age=60 |
^assets/.+$ |
max-age=86400 |
^translate/.+$ |
max-age=14400 |
^.+\.html$ |
max-age=60 |
^.+$ |
max-age=31536000, immutable |
astro: Vite hashes everything under _astro/, and Pagefind (Starlight's search, for example) hashes its index chunks. Everything else is copied from public/ under its original name, so it gets one day.
| Pattern | Cache-Control |
|---|---|
^_astro/.+$ |
max-age=31536000, immutable |
^pagefind/.+\.(pf_meta|pf_index|pf_fragment)$ |
max-age=31536000, immutable |
^.+\.html$ |
max-age=60 |
^.+$ |
max-age=86400 |
custom: there is no way to know how the build names its files, so nothing is cached for a year. To give your hashed folder a long cache, add a rule for it.
| Pattern | Cache-Control |
|---|---|
^.+\.html$ |
max-age=60 |
^.+$ |
max-age=86400 |
The ^.+\.html$ rule keeps every page on the short cache, not only the root index.html. A multi-page build (any static Astro site, or Angular with prerendering) emits files like about/index.html, and those must never be pinned for a year.
Your cacheControlMapping is merged with the defaults rather than replacing them:
- a new pattern is tried before the defaults, in the order you write them;
- a default's pattern overrides its value in place, so overriding
^.+$keeps it last; nullremoves a default.
Patterns are regular expressions tested against the object key — anchor them with ^ and $ when you mean the whole key. A pattern that does not compile, or a value with an unknown, repeated or contradictory directive (no-store with max-age, public with private, …), is rejected when the config loads, before anything is uploaded.
To opt out of the defaults entirely, set useDefaultCacheControl: false. Both your front type's
built-in rules and the built-in defaultCacheControlValue are dropped: only your cacheControlMapping
rules apply, in your order, and a file none of them matches is uploaded without a
Cache-Control header — unless you set defaultCacheControlValue yourself.
bucket: {
front: {
useDefaultCacheControl: false,
cacheControlMapping: {
'^_astro/.+$': 'max-age=31536000, immutable',
},
// defaultCacheControlValue: 'no-cache', // optional fallback
},
},For example, on an Angular front whose build doesn't hash filenames, shorten the catch-all so you don't serve stale assets:
import { Config } from '@czfabrics/front-ready'
export default {
bucket: {
front: {
cacheControlMapping: {
'^fonts/.+$': 'max-age=604800', // added: tried before the defaults
'^.+$': 'max-age=3600', // overridden in place: still the last rule
'^translate/.+$': null, // removed
},
indexDocumentSuffix: 'index.html',
errorDocumentKey: 'index.html',
},
accessMode: 'acl', // or 'policy' for AWS S3 — see "Create" below
upload: {
concurrency: 50, // the cap on files in flight, across every folder at once
exclude: ['\\.map$'], // keys not to upload — here, source maps
},
},
} satisfies ConfigRun the CLI with your package manager's runner — bunx, yarn dlx, or npx.
Creates the bucket and prepares it for static hosting: makes it publicly readable — through ACLs, or through a bucket policy with accessMode: 'policy' — and adds the static website configuration. Running it against a bucket that already exists is a no-op.
How the bucket is made publicly readable depends on bucket.accessMode:
accessMode |
Bucket | Objects | Use with |
|---|---|---|---|
'acl' (default) |
public-read bucket ACL |
public-read ACL each |
Most S3-compatible providers |
'policy' |
BucketOwnerEnforced, Block Public Access lifted, read by policy |
No ACL | AWS S3, which disables ACLs by default |
AWS rejects ACLs on a bucket whose ACLs are disabled — the default for new AWS buckets — so use 'policy' there.
bunx @czfabrics/front-ready create # Bun
yarn dlx @czfabrics/front-ready create # Yarn
npx @czfabrics/front-ready create # npmVerifies that your build produces randomly named (content-hashed) chunk files, which the default cache configuration relies on. Also checks that the configured bucket exists and that it contains at least one object.
Every check reports, then check exits non-zero if the bucket does not exist — so it can gate a CI pipeline ahead of deploy.
bunx @czfabrics/front-ready check # Bun
yarn dlx @czfabrics/front-ready check # Yarn
npx @czfabrics/front-ready check # npmRuns the prebuild command if one is configured, builds your frontend using the configuration above, then uploads the output to the bucket. The file matching indexDocumentSuffix (default: index.html) is uploaded last — so the new entry point only becomes available once all the hashed chunks it references are already in place, avoiding a window where clients load an index.html pointing at chunks that haven't been uploaded yet.
bunx @czfabrics/front-ready deploy # Bun
yarn dlx @czfabrics/front-ready deploy # Yarn
npx @czfabrics/front-ready deploy # npmdeploy asks for confirmation before it builds. In a pipeline, skip it with --yes (or -y); it
is also skipped whenever the CI environment variable is set — as GitHub Actions, GitLab CI and
most other providers do — unless it is false or 0. The target bucket is still logged.
bunx @czfabrics/front-ready deploy --yesWithout the prompt nothing can be asked, so an Angular project must set
front.angular.configurationName: deploy fails rather than offer the configuration picker.
Every command ends on an outro and reports its outcome through the exit code, so CI can trust it:
| Code | Meaning |
|---|---|
0 |
Success — including create on a bucket that already exists |
1 |
Failure — invalid config, build failure, upload error, missing bucket, … |
130 |
Cancelled — a prompt was declined (or escaped), or Ctrl-C was pressed |
Licensed under MIT.
