Hello! I also had similar troubles with IIS and this is how I solved it, hope it is useful for posterity because working with subdirectories can get tricky on different levels.
Rewrite rule on IIS
After the IIS subdirectory project was setted up I just had to modify the routes via the url rewrite module, a simple rewrite like this would serve the page nicely:
<rewrite>
<rules>
<rule name="Rewrite" patternSyntax="ECMAScript">
<match url="^subdirectory/(.*)" />
<action type="Rewrite" url="http://localhost:4200/{R:1}" />
</rule>
</rules>
</rewrite>
But as @deerob4 mentions, the problem is when the browser requests resources directly from sources that will not match from the above rule:
The above ruleset will not match on any /subdirectory
and will make request to resources that don’t exist; in my root.html.eex
, Live Views and Live Components I’m using only static paths:
# root.html.eex
<script defer type="text/javascript" src="<%= Routes.static_path(@conn, ~s(/js/app.js)) %>"></script>
# liveview or livecomponent
<img src="<%= ~s(/images/logo.svg) %>">
There are many ways to solve this problem:
- More rewrite rules with more specific routes
- Just hardcode the paths or entire urls
But the most “practical” for me was to take advantage of the static path configuration for the web endpoint and plug.
Prepending your addresses with a subdirectory
On your config.exs
or prod.exs
configuration files:
# This will let helper functions like Routes.static_path know there's
# an implicit prepended path called "/subdirectory"
config :northbound, Web.Endpoint,
static_url: [path: "/subdirectory"]
And on your endpoint.ex
module:
# This will take care of actually serving all assets from the
# "/subdirectory" path instead of root. Matching with our rewrites.
plug Plug.Static,
at: "/subdirectory",
from: :app
So now our static pages helpers implicitly add the directory:
iex> Routes.static_path(@conn, Routes.static_path(@conn, ~s(/js/app.js))
"/subdirectory/js/app.js"
But these functions can be hard to use on our liveviews since there’s no @conn
object readily available, we could reference assets in our liveviews via the live helpers, but these are dependant on the router.ex
configuration:
# So if your web endpoint is named Web.Endpoint...
# and your MainPage liveview is routed to "/main/live/page"
iex> Routes.live_url(Web.Endpoint, Web.Liveview)
"/main/live/page"
# Which will not match our rule
There are many ways to solve this:
- Change your router and scope liveviews under
/subdirectory
- Build an intermediary function
- Just hardcode the paths or entire urls
In the end I decided to build a “general” function that worked both on static and live pages:
def static_path(path \\ "") do
static_path = Phoenix.Endpoint.Supervisor.static_path(Web.Endpoint) |> elem(1)
Path.join(["/", static_path, path])
end
Keep in mind I only created this function because I needed to reference assets preferably with a static path from within my liveviews; I could totally be missing a more fitting solution. It is recommendable to test out the desired navigation before committing to any particular solution, because one size doesn’t seem to fit all here.
Connecting to the liveview
Here I prefered to take advantage of the url rewrite IIS module and add:
<rewrite>
<rules>
<rule name="WebSocket" patternSyntax="ECMAScript">
<match url="^live/(.*)" />
<action type="Rewrite" url="http://localhost:4200/live/{R:1}" />
</rule>
<rule name="Rewrite" patternSyntax="ECMAScript">
<match url="^subdirectory/(.*)" />
<action type="Rewrite" url="http://localhost:4200/subdirectory/{R:1}" />
</rule>
<rules>
</rewrite>
If hosting multiple liveview applications the above rule could “clash” with other subdirectories, I guess in that case you could change the /live
endpoint to something like /app-live
.
Sorry for overextending in probably obvious stuff, but it took me a while to get there and I’m hoping this info is useful. Many thanks to @geo’s, his blog post really helped me: Hosting A Phoenix App In A Subdirectory With Nginx.