An Elm web application that uses Browser.application
is typically doing so in order to support client-side routing. Client-side routing is what allows your single-page application to have several views which can be accessed via different URLs without doing a page refresh. Because of the nature of client-side routing and because of how a web browser works when you navigate to a new URL, we need to tell our web server how to handle certain requests so that our web application can respond to those requests instead.
Server-side routing
With server-side routing, the routing is handled by the web server. For e.g.
mkdir website
cd website
echo "Home page" > index.html
mkdir about
echo "About page" > about/index.html
mkdir login
echo "Login page" > login/index.html
nix-shell -p caddy
caddy file-server -l ":3000"
Create a new website
directory with three files, index.html
, about/index.html
, and login/index.html
. Then, install the Caddy web server in an isolated shell and tell it to serve the current directory at http://localhost:3000
.
URL | Resource |
---|---|
http://localhost:3000/ | index.html |
http://localhost:3000/about | about/index.html |
http://localhost:3000/login | login/index.html |
If you go to one of the above URLs using your browser, the browser would send the request to Caddy and Caddy would respond by sending the corresponding file back to the browser. This is how a web server works, i.e. the path in the URL is used by the web server to find a resource (in this case a static HTML file) to send back to a browser. The browser then renders the resource for you to see.
Client-side routing
With client-side routing, the routing is handled in the browser using JavaScript. The web server redirects certain requests to a single HTML page, usually index.html
, which contains the JavaScript that:
- Manages the user's history with the History API. And,
- Hijacks clicks on internal links in order to stop the browser from asking the web server for the resource.
// Detect when the history changes, for e.g. the Back button is pressed
window.addEventListener("popstate", () => {
render(window.location.pathname)
})
// Hijack clicks on anchor tags
document.addEventListener("click", (event) => {
if (
// You clicked an anchor tag
event.target.tagName === "A" &&
// And, you're going to a page on this domain
event.target.origin === window.location.origin
) {
// Don't ask the web server for that resource
event.preventDefault()
// Update the browser's history
window.history.pushState({}, "", event.target.href)
render(event.target.pathname)
}
})
// Figure out which template to render when the page is first loaded
render(window.location.pathname)
Please take a look at the complete client-side routing example.
What happens when you go directly to the About page?
When you go to https://csr-example.netlify.app/about using your browser:
- The browser sends the request to the web server.
- The web server redirects the request to the
index.html
file and sends theindex.html
file back to the browser. - The browser loads the
index.html
file and when the page has loaded it renders the template for the/about
path.
What happens when you click on the Login link?
When you click on the Login link that's on the About page:
- The click is intercepted by the
document
's click handler. - It checks if you clicked on an internal link.
- If you did, then:
- It stops the browser from requesting the resource from the web server.
- It manually updates the browser's history using the History API.
- And, it renders the template for the
/login
path.
Making client-side routing work for your Elm web application
Now that you understand how client-side routing works you'd be happy to know that Browser.application
handles it all for you except for the web server configuration part.
Caddy
To configure Caddy you have to use the try_files directive. See here for an example.
Netlify
Netlify allows you to use a _redirects
file in your website's directory. See here for an example.
GitHub
GitHub doesn't allow you to configure the web server. However, there are workarounds if you really want to use GitHub.
Other hosting providers
These are the other hosting providers I've used that also work.
Examples
Here are some examples of Elm web applications that use Browser.application
.
-
Todos
- Website: https://elm-todos.netlify.app/
- Public directory: https://github.com/dwayne/elm-todos/tree/netlify
-
Super Rentals
- Website: https://elm-super-rentals.netlify.app/
- Public directory: https://github.com/dwayne/elm-super-rentals/tree/netlify
-
Built with Elm
- Website: https://www.builtwithelm.co/
- Public directory: https://github.com/dwayne/builtwithelm/tree/netlify
-
Conduit
- Website: https://elm-conduit.netlify.app/
- Public directory: https://github.com/dwayne/elm-conduit/tree/production
- To get a full tour of this application, you can read Yet Another Tour of an Open-Source Elm SPA.
Bonus: Under the hood of Browser.application
Let's dig into the code for elm/browser to see for ourselves what Browser.application
really does.
- Start here in
Browser.elm
. - Immediately we see that it's actually implemented in JavaScript, here.
- On this line it sets up a handler for the
popstate
event so that it can manage the browser's history using the History API. - And here, it implements the logic for hijacking clicks on anchor tags.
- Lastly, you can see here how
init
gets the initial URL so that you can show different things when the page is first loaded.
I hope you're less intimidated by Browser.application
now that you see how similar it works to the client-side routing example.
Conclusion
Browser.application
is used when you want to support client-side routing. However, it requires you to be able to configure your web server to redirect requests to your index.html
file. Since GitHub Pages doesn't allow you to customize their web server, you're forced to switch to another hosting service like Netlify, Render, or Cloudflare Pages. Thankfully, they all make it easy to do the configuration that's needed.
Further reading
- How I host Elm web applications with GitHub Pages
- Introduction to client-side frameworks: Routing
- Create React App: Notes on client-side routing
- Vue CLI: Routing with
history.pushState
Subscribe to my newsletter
If you're interested in improving your skills with Elm then I invite you to subscribe to my newsletter, Elm with Dwayne. To learn more about it, please read this announcement.
Top comments (0)