Serving from a different Plug.Static `at:` emits verified routes warnings for assets

I configure Plug.Static to:

  plug Plug.Static,
    at: "/admin",

and point the assets to it:

<link phx-track-static rel="stylesheet" href={~p"/admin/assets/app.css"} />

This serves the web page correctly, but I am warned against it:

warning: no route path for MyWeb.Router matches "/admin/assets/app.css"
  lib/my_web/components/layouts/root.html.heex:10: MyWeb.Layouts.root/1

CI won’t let warnings through, so…

Attempt 1

I tried working with the endpoint :static_url config, but could not figure out a way to make this config help. In fact, I’m not sure how this would be used now that we have verified routes? Is this route helper specific?

config :my_web, MyWeb.Endpoint
  static_url: [path: "/admin"]

Attempt 2

I reset the at: to root

plug Plug.Static,
  at: "/",

and changed static_paths/0 to hold the prefix

def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) |>"admin", &1))

but this returned 404s for app.css

Workable Solution

I was able to solve the warnings with the below

use Phoenix.VerifiedRoutes,
  endpoint: MyWeb.Endpoint,
  router: MyWeb.Router,
  statics:, &Path.join("admin", &1))

but it didn’t quite feel ideal. I’m wondering if there’s something more direct that I’m missing.

The :statics setting of Phoenix.VerifiedRoutes expects a list of top level folder or filenames (similar to the Plug.Static :only setting). It does not take full paths. In your case that would just be ["admin"] if everything is within that folder.


I may have confused the issue with my lack of understanding in attempt #1.

I have two endpoints, and I’m using main_proxy to point /admin at one of them, so there’s a full phoenix app I want to serve, it’s not just static files at /admin, so setting statics to [“admin”] would not work (if I’m understanding you correctly)

What I’m curious about is if there’s a more idiomatic way to serve static files not at “/” and let VerifiedRoutes know that I’ve done so.

Then you should use the :url config on ThatApp.Endpoint. That will scope everything to that subfolder.

I hoped this suggestion was a silver bullet, but it wasn’t. I opened a new app, no umbrella/multiple endpoints/main_proxy/etc. I know that url: [path: ..] is not enough because it only changes generated urls, not the routes, but at the least LiveReloader doesn’t know about these renames and throws an error that I don’t see a way around without disabling the reloader.

[info] GET /admin/phoenix/live_reload/frame
[debug] ** (Phoenix.Router.NoRouteError) no route found for GET /admin/phoenix/live_reload/frame (MyAppWeb.Router)
    (my_app 0.1.0) lib/phoenix/router.ex:482:
    (my_app 0.1.0) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
    (my_app 0.1.0) lib/plug/debugger.ex:136: MyAppWeb.Endpoint."call (overridable 3)"/2
    (my_app 0.1.0) lib/my_app_web/endpoint.ex:1:
    (phoenix 1.7.1) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (plug_cowboy 2.6.1) lib/plug/cowboy/handler.ex:11: Plug.Cowboy.Handler.init/2

My guess is that url: generation rewriting is generally used when behind a reverse proxy in production, so you wouldn’t have a reloader there, and thus this hasn’t come up? The server specifics of the front end are one of my weakest subjects.

I think I’ve tracked the above problem to phoenix_live_reload/lib/phoenix_live_reload/live_reloader.ex:119 where the pattern match on path_info doesn’t allow for generated url: [path: ..] prefix

  def call(%Plug.Conn{path_info: ["phoenix", "live_reload", "frame" | _suffix]} = conn, _) do
    endpoint = conn.private.phoenix_endpoint

I hope to do some more spelunking, especially as how main proxy is involved, but so far the original appeasement of verified routes statics: is my working solution.