Phoenix static assets (favicons, apple-touch-icon, site.webmanifest) at root level return 404 in production when digested, even though files like robots.txt work fine. The issue is that `static_paths` with full filenames like `favicon.ico` don't match digested filenames like `favicon-b95efca76c4b65917de1b6a15db8d653.ico` because Plug.Static's `only` option uses `String.starts_with?` - and `favicon-hash.ico` doesn't start with `favicon.ico`.
Move all icon files into a subfolder and reference them by folder name in static_paths:
-
Create
priv/static/icons/and move favicon.ico, favicon.svg, apple-touch-icon.png, site.webmanifest, etc. into it -
Update
static_pathsinlib/your_app_web.ex:
def static_paths, do: ~w(assets fonts images icons robots.txt llms.txt)
- Update template references in
root.html.heex:
<link rel="icon" href={~p"/icons/favicon.ico"} sizes="any" />
<link rel="icon" href={~p"/icons/favicon.svg"} type="image/svg+xml" />
<link rel="apple-touch-icon" href={~p"/icons/apple-touch-icon.png"} />
<link rel="manifest" href={~p"/icons/site.webmanifest"} />
- Update paths inside site.webmanifest to use
/icons/prefix
This works because folder names like icons match both /icons/favicon.ico and /icons/favicon-hash.ico via starts_with. Same pattern as assets/ which already works.