Asset Pipeline configuration
Supported web path: This is the asset contract generated by Amber CLI
2.0.6for Amber2.0.0-beta.5and asset_pipeline0.37.0.
Complete Asset Pipeline first. Every filesystem path below is resolved
from the application root, beside shard.yml.
Keep build-time and runtime responsibilities separate
File: config/assets.cr — this complete generated file configures only the
runtime manifest resolver.
Amber::Assets.configure(
manifest_path: "public/assets/manifest.json"
)
Do not require the compiler from config/assets.cr. That file is loaded into
the running server, which only needs Amber's resolver. Keeping the compiler in
the CLI or a build script prevents release tooling from becoming an accidental
runtime dependency.
The three paths have different owners:
| Setting | Value | Owner |
|---|---|---|
source_root |
app/assets |
source files developers edit |
output_root |
public/assets |
generated release files |
public_path |
/assets |
URLs emitted into the manifest |
Never point source_root and output_root at the same directory. Never store
uploads in either directory. The build is allowed to replace generated output;
it must not delete application source or runtime data.
Nested logical paths are preserved.
Reference structure — authored source files:
app/assets/stylesheets/app.css
app/assets/javascript/controllers/menu.js
app/assets/images/marketing/hero.webp
app/assets/fonts/Manrope-Variable.woff2
remain distinct logical entries with the same relative paths, even though the
emitted filenames include content digests. Directory preservation prevents two
files such as admin/logo.svg and store/logo.svg from colliding.
File: scripts/build_assets.cr — an existing pre-2.0.5 application can use
this complete build-only wrapper.
require "asset_pipeline/static_assets"
AssetPipeline::StaticAssets::Compiler.new(
source_root: Path["app/assets"],
output_root: Path["public/assets"],
public_path: "/assets"
).build
Configure Amber's resolver
File: src/my_app.cr — verify the application entry point loads top-level
configuration before controllers and models.
require "../config/*"
Replace my_app with the target name. The generated V2 entry point uses that
wildcard, so config/assets.cr is loaded. If a migrated app has a narrower
require list, add require "../config/assets" explicitly after the
configuration file that loads Amber. Do not put the setup in the empty
config/initializers/ directory unless the app explicitly requires it.
The resolver is strict for logical paths. A missing entry is a build or deploy
failure to fix, not a reason to fall back silently to an unhashed URL. External
URLs, absolute application paths, fragments, and data: URLs pass through.
Inspect the manifest directly
Application views should normally use Amber's helpers. Build tooling can load the same manifest directly when it needs structured metadata.
File: a build verification program, for example
scripts/verify_assets.cr — create this complete file.
require "asset_pipeline/static_assets"
manifest = AssetPipeline::StaticAssets::Manifest.load(
Path["public/assets/manifest.json"]
)
manifest.verify(Path["public/assets"])
puts manifest.path("stylesheets/app.css")
puts manifest.integrity("stylesheets/app.css")
entry = manifest.entry("fonts/Manrope-Variable.woff2")
puts "#{entry.content_type} #{entry.bytes} bytes"
Each entry records its public path, full SHA-256 digest, SRI value, content type,
and byte count. verify checks those values against the emitted bytes and
deterministic gzip companions. path, integrity, and entry are strict
lookups; a miss stops release verification.
Run from: the application root, after building assets.
crystal run scripts/verify_assets.cr
CSS references
File: app/assets/stylesheets/app.css — use paths relative to this source
stylesheet for local authored files.
@font-face {
font-family: "Manrope";
src: url("../fonts/Manrope-Variable.woff2") format("woff2");
font-display: swap;
}
.hero {
background-image: url("../images/marketing/hero.webp");
}
The build rewrites those local references to the fingerprinted public paths. Keep an external URL, root-absolute URL, fragment, or data URL only when that is deliberately outside the manifest. A missing relative file is an error.
Relative CSS @import references are rewritten too. Query strings and fragments
on a local reference are preserved after the fingerprinted path. The compiler
also rewrites relative static imports, exports, dynamic imports, and source-map
references in browser-ready JavaScript. Bare module names stay unchanged so an
import map can resolve them.
Asset Pipeline copies the bytes supplied to it. Generate real responsive image
sizes and formats in an earlier deterministic build step if the application
needs them, then put every emitted variant under app/assets/images/ and list
the real logical paths in srcset or <picture>. Query parameters such as
?w=640 or ?format=webp do not create an image variant.
Development workflow
Rebuild assets after an authored source file changes, then let the Amber watcher reload application code.
Run from: the application root.
amber assets build
amber watch
amber watch already runs the same compiler before application compilation
when app/assets/**/* changes. Running amber assets build explicitly is
useful before the initial watcher start and when diagnosing output. The compiler
is never a first-request hook.
Production workflow
Run from: the application root — build before compiling or packaging the application.
shards install --production
amber assets build
amber assets check
crystal spec
shards build my_app --release
For an older app that uses scripts/build_assets.cr, run that file instead of
amber assets build, then run scripts/verify_assets.cr. Both paths invoke the
same asset_pipeline 0.37.0 manifest contract.
Package bin/my_app, config/, and the complete generated public/assets/
tree. Start the runtime with a read-only release directory. A writable
public/assets/ path or a warm-up request must never be required.
The compiler writes files atomically, publishes manifest.json last, and after
a successful rebuild removes stale files owned by the previous manifest. It
does not delete unrelated files under public/assets/. Deployment still must
copy or switch the complete generated tree as one unit.
Deploy atomically: place a complete release in a new directory, verify it, then switch traffic. Rollback switches back to the prior complete directory. Do not copy new files over an old asset tree, and do not share a manifest between releases.
Cache boundary
Reference response header — apply only to fingerprinted asset URLs:
Cache-Control: public, max-age=31536000, immutable
HTML and public/assets/manifest.json must revalidate or use a short cache so
clients can discover a new deployment. Unfingerprinted aliases must never be
cached as immutable. Configure compression in Amber's static handler or the
reverse proxy, and verify Content-Type, Content-Encoding, and Vary rather
than assuming a CDN corrected them.
Release verification
Verify at least one CSS file, JavaScript module, image, font, and other binary:
- build assets from a clean checkout;
- load
manifest.jsonand perform strict lookups; - start the compiled app with the release directory read-only;
- request every emitted URL and check bytes and content type;
- confirm fingerprinted responses receive immutable caching;
- confirm HTML and the manifest do not;
- edit each source class, rebuild, and confirm its URL changes; and
- switch back to the prior complete release and confirm its URLs still work.