{"title":"Configuration","description":"Configure the Asset Pipeline compiler, runtime manifest, and release boundary","section":"guides/assets","version":"v2","path":"guides/assets/configuration","canonical_url":"https://amberframework.org/docs/v2/guides/assets/configuration","markdown_url":"https://amberframework.org/docs/v2/guides/assets/configuration.md","inherited":false,"content_markdown":"# Asset Pipeline configuration\n\n> **Supported web path:** This is the asset contract generated by Amber CLI\n> `2.0.6` for Amber `2.0.0-beta.5` and asset_pipeline `0.37.0`.\n\nComplete [Asset Pipeline](../) first. Every filesystem path below is resolved\nfrom the application root, beside `shard.yml`.\n\n## Keep build-time and runtime responsibilities separate\n\n**File: `config/assets.cr` — this complete generated file configures only the\nruntime manifest resolver.**\n\n```crystal\nAmber::Assets.configure(\n  manifest_path: \"public/assets/manifest.json\"\n)\n```\n\nDo not require the compiler from `config/assets.cr`. That file is loaded into\nthe running server, which only needs Amber's resolver. Keeping the compiler in\nthe CLI or a build script prevents release tooling from becoming an accidental\nruntime dependency.\n\nThe three paths have different owners:\n\n| Setting | Value | Owner |\n|---|---|---|\n| `source_root` | `app/assets` | source files developers edit |\n| `output_root` | `public/assets` | generated release files |\n| `public_path` | `/assets` | URLs emitted into the manifest |\n\nNever point `source_root` and `output_root` at the same directory. Never store\nuploads in either directory. The build is allowed to replace generated output;\nit must not delete application source or runtime data.\n\nNested logical paths are preserved.\n\n**Reference structure — authored source files:**\n\n```text\napp/assets/stylesheets/app.css\napp/assets/javascript/controllers/menu.js\napp/assets/images/marketing/hero.webp\napp/assets/fonts/Manrope-Variable.woff2\n```\n\nremain distinct logical entries with the same relative paths, even though the\nemitted filenames include content digests. Directory preservation prevents two\nfiles such as `admin/logo.svg` and `store/logo.svg` from colliding.\n\n**File: `scripts/build_assets.cr` — an existing pre-2.0.5 application can use\nthis complete build-only wrapper.**\n\n```crystal\nrequire \"asset_pipeline/static_assets\"\n\nAssetPipeline::StaticAssets::Compiler.new(\n  source_root: Path[\"app/assets\"],\n  output_root: Path[\"public/assets\"],\n  public_path: \"/assets\"\n).build\n```\n\n## Configure Amber's resolver\n\n**File: `src/my_app.cr` — verify the application entry point loads top-level\nconfiguration before controllers and models.**\n\n```crystal\nrequire \"../config/*\"\n```\n\nReplace `my_app` with the target name. The generated V2 entry point uses that\nwildcard, so `config/assets.cr` is loaded. If a migrated app has a narrower\nrequire list, add `require \"../config/assets\"` explicitly after the\nconfiguration file that loads Amber. Do not put the setup in the empty\n`config/initializers/` directory unless the app explicitly requires it.\n\nThe resolver is strict for logical paths. A missing entry is a build or deploy\nfailure to fix, not a reason to fall back silently to an unhashed URL. External\nURLs, absolute application paths, fragments, and `data:` URLs pass through.\n\n## Inspect the manifest directly\n\nApplication views should normally use Amber's helpers. Build tooling can load\nthe same manifest directly when it needs structured metadata.\n\n**File: a build verification program, for example\n`scripts/verify_assets.cr` — create this complete file.**\n\n```crystal\nrequire \"asset_pipeline/static_assets\"\n\nmanifest = AssetPipeline::StaticAssets::Manifest.load(\n  Path[\"public/assets/manifest.json\"]\n)\nmanifest.verify(Path[\"public/assets\"])\n\nputs manifest.path(\"stylesheets/app.css\")\nputs manifest.integrity(\"stylesheets/app.css\")\nentry = manifest.entry(\"fonts/Manrope-Variable.woff2\")\nputs \"#{entry.content_type} #{entry.bytes} bytes\"\n```\n\nEach entry records its public path, full SHA-256 digest, SRI value, content type,\nand byte count. `verify` checks those values against the emitted bytes and\ndeterministic gzip companions. `path`, `integrity`, and `entry` are strict\nlookups; a miss stops release verification.\n\n**Run from: the application root, after building assets.**\n\n```bash\ncrystal run scripts/verify_assets.cr\n```\n\n## CSS references\n\n**File: `app/assets/stylesheets/app.css` — use paths relative to this source\nstylesheet for local authored files.**\n\n```css\n@font-face {\n  font-family: \"Manrope\";\n  src: url(\"../fonts/Manrope-Variable.woff2\") format(\"woff2\");\n  font-display: swap;\n}\n\n.hero {\n  background-image: url(\"../images/marketing/hero.webp\");\n}\n```\n\nThe build rewrites those local references to the fingerprinted public paths.\nKeep an external URL, root-absolute URL, fragment, or data URL only when that is\ndeliberately outside the manifest. A missing relative file is an error.\n\nRelative CSS `@import` references are rewritten too. Query strings and fragments\non a local reference are preserved after the fingerprinted path. The compiler\nalso rewrites relative static imports, exports, dynamic imports, and source-map\nreferences in browser-ready JavaScript. Bare module names stay unchanged so an\nimport map can resolve them.\n\nAsset Pipeline copies the bytes supplied to it. Generate real responsive image\nsizes and formats in an earlier deterministic build step if the application\nneeds them, then put every emitted variant under `app/assets/images/` and list\nthe real logical paths in `srcset` or `<picture>`. Query parameters such as\n`?w=640` or `?format=webp` do not create an image variant.\n\n## Development workflow\n\nRebuild assets after an authored source file changes, then let the Amber watcher\nreload application code.\n\n**Run from: the application root.**\n\n```bash\namber assets build\namber watch\n```\n\n`amber watch` already runs the same compiler before application compilation\nwhen `app/assets/**/*` changes. Running `amber assets build` explicitly is\nuseful before the initial watcher start and when diagnosing output. The compiler\nis never a first-request hook.\n\n## Production workflow\n\n**Run from: the application root — build before compiling or packaging the\napplication.**\n\n```bash\nshards install --production\namber assets build\namber assets check\ncrystal spec\nshards build my_app --release\n```\n\nFor an older app that uses `scripts/build_assets.cr`, run that file instead of\n`amber assets build`, then run `scripts/verify_assets.cr`. Both paths invoke the\nsame asset_pipeline `0.37.0` manifest contract.\n\nPackage `bin/my_app`, `config/`, and the complete generated `public/assets/`\ntree. Start the runtime with a read-only release directory. A writable\n`public/assets/` path or a warm-up request must never be required.\n\nThe compiler writes files atomically, publishes `manifest.json` last, and after\na successful rebuild removes stale files owned by the previous manifest. It\ndoes not delete unrelated files under `public/assets/`. Deployment still must\ncopy or switch the complete generated tree as one unit.\n\nDeploy atomically: place a complete release in a new directory, verify it, then\nswitch traffic. Rollback switches back to the prior complete directory. Do not\ncopy new files over an old asset tree, and do not share a manifest between\nreleases.\n\n## Cache boundary\n\n**Reference response header — apply only to fingerprinted asset URLs:**\n\n```text\nCache-Control: public, max-age=31536000, immutable\n```\n\nHTML and `public/assets/manifest.json` must revalidate or use a short cache so\nclients can discover a new deployment. Unfingerprinted aliases must never be\ncached as immutable. Configure compression in Amber's static handler or the\nreverse proxy, and verify `Content-Type`, `Content-Encoding`, and `Vary` rather\nthan assuming a CDN corrected them.\n\n## Release verification\n\nVerify at least one CSS file, JavaScript module, image, font, and other binary:\n\n1. build assets from a clean checkout;\n2. load `manifest.json` and perform strict lookups;\n3. start the compiled app with the release directory read-only;\n4. request every emitted URL and check bytes and content type;\n5. confirm fingerprinted responses receive immutable caching;\n6. confirm HTML and the manifest do not;\n7. edit each source class, rebuild, and confirm its URL changes; and\n8. switch back to the prior complete release and confirm its URLs still work."}