# Pages & routing

__Source code__: [GitHub](https://github.com/ryan-haskell/elm-spa/tree/main/examples/02-pages)

This next guide will show you how pages, routing, and the `elm-spa add` command work together to automatically handle URLs in your __elm-spa__ application.


### The setup

Just like with the last guide, we can use `elm-spa new` and `elm-spa server` to get a brand new __elm-spa__ project up and running:

```terminal
elm-spa new
```

This generates the "Hello, world!" homepage from before:

```terminal
elm-spa server
```

![A browser displaying "Hello world"](/content/images/01-hello-world.png)

### Adding a static page

```terminal
elm-spa add /static static
```

This command adds a page at [http://localhost:1234/static](http://localhost:1234/static) with the `static` template. This is similar to `Home_.elm`, but it has access to `Shared.Model` and `Request` in case we need data from either of those.

Here is the complete `Static.elm` file:

```elm
module Pages.Static exposing (page)

import Page exposing (Page)
import Request exposing (Request)
import Shared
import View exposing (View)


page : Shared.Model -> Request -> Page
page shared req =
    Page.static
        { view = view
        }


view : View msg
view =
    View.placeholder "Static"
```

The `View.placeholder` function just stubs out the `view` function with an empty page that only renders "Static" in the browser.

Visit [http://localhost:1234/static](http://localhost:1234/static) to see it in action!

### Making a layout

Before we continue, I want to make a layout with a navbar so that we can easily navigate between pages without manually editing the URL.

I'll create a file at `src/UI.elm` that looks like this:

```elm
module UI exposing (layout)

import Html exposing (Html)
import Html.Attributes as Attr


layout : List (Html msg) -> List (Html msg)
layout children =
    let
        viewLink : String -> String -> Html msg
        viewLink label url =
            Html.a [ Attr.href url ] [ Html.text label ]
    in
    [ Html.div [ Attr.class "container" ]
        [ Html.header [ Attr.class "navbar" ]
            [ viewLink "Home" "/"
            , viewLink "Static" "/static"
            ]
        , Html.main_ [] children
        ]
    ]
```

### Using the layout in a page

Because it works from one `List (Html msg)` to another, we can add `UI.layout` in front of the `body` list on both pages:

```elm
-- src/Pages/Home_.elm

view : View msg
view =
    { title = "Homepage"
    , body = UI.layout [ Html.text "Homepage" ]
    }
```

```elm
-- src/Pages/Static.elm

view : View msg
view =
    { title = "Static"
    , body = UI.layout [ Html.text "Static" ]
    }
```

### Use routes, not strings

In `src/UI.elm`, we had a function for rendering our navbar links that looked like this:

```elm
viewLink : String -> String -> Html msg
viewLink label url =
    Html.a [ Attr.href url ] [ Html.text label ]
```

This function works great– but it's possible to provide a URL that our application doesn't have!

```elm
[ viewLink "Home" "/"
, viewLink "Static" "/satic"
]
```

Here, I mistyped the URL `/satic`, but the compiler didn't warn me about it! Let's use the `Route` values generated by __elm-spa__ to improve this experience:

```elm
import Gen.Route as Route exposing (Route)

viewLink : String -> Route -> Html msg
viewLink label route =
    Html.a [ Attr.href (Route.toHref route) ] [ Html.text label ]
```

By using the `Gen.Route` module from `.elm-spa/generated`, we can pass in a `Route` instead of a `String`:

```elm
[ viewLink "Home" Route.Home_ 
, viewLink "Static" Route.Static
]
```

This will prevent typos, but __more importantly__ it allows the Elm compiler to remind us to update the navbar in case we remove `Home_.elm` or `Static.elm` in the future.

Deleting either of those pages changes the generated `Gen.Route` module, so the compiler can let us know that our `UI.layout` function has a broken link– before our users do!

### Adding CSS

In `UI.layout`, we used `Attr.class` to provide our HTML with some CSS classes:

```elm
Html.div [ Attr.class "container" ]
    [ Html.header [ Attr.class "navbar" ]
        [ viewLink "Home" Route.Home_
        , viewLink "Static" Route.Static
        ]
    ]
```

The `container` and `navbar` classes are used in our code, but not defined in a CSS file. Let's fix that by creating a new CSS file at `public/style.css`:

```css
.container {
  max-width: 960px;
  margin: 1rem auto;
}

.navbar {
  display: flex;
  align-items: center;
}

.navbar a {
  margin-right: 16px;
}
```

After creating `style.css`, we can import the file in our `public/index.html` entrypoint:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- import our new CSS file -->
  <link rel="stylesheet" href="/style.css">

</head>
<body>
  <script src="/dist/elm.js"></script>
  <script> Elm.Main.init() </script>
</body>
</html>
```

Using the `<link>` tag as shown above (with the leading slash!) imports our CSS file. All files in the `public` folder are available at the root of our web application. That means a file stored at `public/images/dog.png` would be at `http://localhost:1234/images/dog`, without including `public` in the URL at all.

### Adding more page types

```terminal
elm-spa add /sandbox sandbox
elm-spa add /element element
elm-spa add /advanced advanced
```

These commands add in the other three page types described in the [pages guide](/guides/03-pages).

For each page, the `View.placeholder` function stubs out the `view` functions so you can visit them in the browser.

For example, [http://localhost:1234/element](http://localhost:1234/element) should display "Element" on the screen.

### Adding some dynamic routes

To add in dynamic routes, we can use `elm-spa add` again:

```terminal
elm-spa add /dynamic/:name static
```

With this command, we just created a page at `src/Pages/Dynamic/Name_.elm`. When a user visits a URL like `/dynamic/ryan` or `dynamic/123`, we'll be taken to this page.

Let's tweak the default `view` function to render the dynamic `name` parameter from the URL:

```elm
-- src/Pages/Dynamic/Name_.elm

view : Params -> View msg
view params =
    { title = "Dynamic: " ++ params.name
    , body =
        UI.layout
            [ UI.h1 "Dynamic Page"
            , Html.h2 [] [ Html.text params.name ]
            ]
    }
```

We can provide in the `req.params` to the `view` function by telling our `page` function to pass it along:

```elm
page : Shared.Model -> Request.With Params -> Page
page _ req =
    Page.static    -- 👇 we pass in params here
        { view = view req.params 
        }          
```

### Updating the navbar

Once we wire up these pages to use `UI.layout`, we can add links to the navbar:

```elm
-- src/UI.elm
import Gen.Route as Route

layout : List (Html msg) -> List (Html msg)
layout children =
    let
        viewLink : String -> Route -> Html msg
        viewLink label route =
            Html.a [ Attr.href (Route.toHref route) ] [ Html.text label ]
    in
    [ Html.div [ Attr.class "container" ]
        [ Html.header [ Attr.class "navbar" ]
            [ Html.strong [ Attr.class "brand" ] [ viewLink "Home" Route.Home_ ]
            , viewLink "Static" Route.Static
            , viewLink "Sandbox" Route.Sandbox
            , viewLink "Element" Route.Element
            , viewLink "Advanced" Route.Advanced
            , Html.div [ Attr.class "splitter" ] []
            , viewLink "Dynamic: Apple" (Route.Dynamic__Name_ { name = "apple" })
            , viewLink "Dynamic: Banana" (Route.Dynamic__Name_ { name = "banana" })
            ]
        , Html.main_ [] children
        ]
    ]
```

#### That's it!

Feel free to play around with the `elm-spa add` command to mix-and-match different pages. 

As always, the source code for this example is available on [GitHub](https://github.com/ryan-haskell/elm-spa/tree/main/examples/02-pages)

---

__Next up:__ [Storage](./03-storage)
