Whether your data is coming from markdown files, APIs, a CMS, or all of the above, elm-pages lets you pull in just the data you need for a page. No loading spinners, no Msg or update logic, just define your data and use it in your view.
module Route.Repo.Name_ exposing (Data, Model, Msg, route)
type alias Data = Int
type alias RouteParams = { name : String }
route : StatelessRoute RouteParams Data ActionData
route =
    RouteBuilder.preRender
        { head = head
        , pages = pages
        , data = data
        }
        |> RouteBuilder.buildNoState { view = view }
pages : BackendTask error (List RouteParams)
pages =
    BackendTask.succeed [ { name = "elm-pages" } ]
data : RouteParams -> BackendTask FatalError Data
data routeParams =
    BackendTask.Http.getJson
        (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
        (Decode.field "stargazer_count" Decode.int)
    |> BackendTask.allowFatal
view :
    App Data ActionData RouteParams
    -> View Msg
view app =
    { title = app.routeParams.name
    , body =
        [ h1 [] [ text app.routeParams.name ]
        , p [] [ text ("Stars: " ++ String.fromInt app.data) ]
        ]
    }Wherever the data came from, you can transform BackendTasks and combine multiple BackendTasks using the full power of Elm's type system.
type alias Project =
    { name : String
    , description : String
    , stars : Int
    }
all : BackendTask FatalError (List Project)
all =
    Glob.succeed
        (\projectName filePath ->
            BackendTask.map3 Project
                (BackendTask.succeed projectName)
                (BackendTask.File.rawFile filePath BackendTask.File.body |> BackendTask.allowFatal)
                (stars projectName)
        )
        |> Glob.match (Glob.literal "projects/")
        |> Glob.capture Glob.wildcard
        |> Glob.match (Glob.literal ".txt")
        |> Glob.captureFilePath
        |> Glob.toBackendTask
        |> BackendTask.allowFatal
        |> BackendTask.resolve
stars : String -> BackendTask Int
stars repoName =
    Decode.field "stargazers_count" Decode.int
    |> BackendTask.Http.getJson ("https://api.github.com/repos/dillonkearns/" ++ repoName)
    |> BackendTask.allowFatal
Make sure your site previews look polished with the type-safe SEO API. `elm-pages build` pre-renders HTML for your pages. And your SEO tags get access to the page's BackendTasks.
head :
    App Data ActionData RouteParams
    -> List Head.Tag
head app =
    Seo.summaryLarge
        { canonicalUrlOverride = Nothing
        , siteName = "elm-pages"
        , image =
            { url = app.data.image
            , alt = app.data.description
            , dimensions = Nothing
            , mimeType = Nothing
            }
        , description = app.data.description
        , locale = Nothing
        , title = app.data.title
        }
        |> Seo.article
            { tags = []
            , section = Nothing
            , publishedTime = Just (Date.toIsoString app.data.published)
            , modifiedTime = Nothing
            , expirationTime = Nothing
            }
With server-rendered routes, you can seamlessly pull in user-specific data from your backend and hydrate it into a dynamic Elm application. No API layer required. You can access incoming HTTP requests from your server-rendered routes, and even use the Session API to manage key-value pairs through signed cookies.
module Route.Feed exposing (ActionData, Data, Model, Msg, RouteParams, route)
type alias RouteParams = {}
type alias Data =
    { user : User
    , posts : List Post
    }
data :
    RouteParams
    -> Request
    -> BackendTask FatalError (Response Data ErrorPage)
data routeParams request =
    request
    |> withUserOrRedirect
        (\user ->
            BackendTask.map (Data user)
            (BackendTask.Custom.run "getPosts"
                (Encode.string user.id)
                (Decode.list postDecoder)
                |> BackendTask.allowFatal
            )
                |> BackendTask.map Server.Response.render
        )
withUserOrRedirect :
    (User -> BackendTask FatalError (Response Data ErrorPage))
    -> Request
    -> BackendTask FatalError (Response Data ErrorPage)
withUserOrRedirect withUser request =
    request
        |> Session.withSession
            { name = "session"
            , secrets =
                BackendTask.Env.expect "SESSION_SECRET"
                    |> BackendTask.allowFatal
                    |> BackendTask.map List.singleton
            , options = Nothing
            }
            (\() session ->
                session
                    |> Session.get "sessionId"
                    |> Maybe.map getUserFromSession
                    |> Maybe.map (BackendTask.andThen withUser)
                    |> Maybe.withDefault (BackendTask.succeed (Route.redirectTo Route.Login))
                    |> BackendTask.map (Tuple.pair session)
            )
getUserFromSession : String -> BackendTask FatalError User
getUserFromSession sessionId =
    BackendTask.Custom.run "getUserFromSession"
        (Encode.string sessionId)
        userDecoder
        |> BackendTask.allowFatal
view :
    App Data ActionData RouteParams
    -> Shared.Model
    -> Model
    -> View (PagesMsg Msg)
view app shared model =
    { title = "Feed"
    , body =
        [ navbarView app.data.user
        , postsView app.data.posts
        ]
    }
elm-pages uses progressively enhanced web standards. The Web has had a way to send data to backends for decades, no need to re-invent the wheel! Just modernize it with some progressive enhancement. You define your Form and validations declaratively, and elm-pages gives you client-side validations and state with no Model/init/update wiring whatsoever. You can even derive pending/optimistic UI from the in-flight form submissions (which elm-pages manages and exposes to you for free as well!).
module Route.Signup exposing (ActionData, Data, Model, Msg, RouteParams, route)
type alias Data = {}
type alias RouteParams = {}
type alias ActionData = { errors : Form.Response String }
route : RouteBuilder.StatefulRoute RouteParams Data ActionData Model Msg
route =
    RouteBuilder.serverRender { data = data, action = action, head = head }
        |> RouteBuilder.buildNoState { view = view }
type alias ActionData =
    { errors : Form.Response String }
view :
    App Data ActionData RouteParams
    -> Shared.Model
    -> View (PagesMsg Msg)
view app shared =
    { title = "Sign Up"
    , body =
        [ Html.h2 [] [ Html.text "Sign Up" ]
        -- client-side validation wiring is managed by the framework
        , Form.renderHtml "signup" [] (Just << .errors) app () signUpForm
        ]
    }
data :
    RouteParams
    -> Request
    -> BackendTask FatalError (Response Data ErrorPage)
data routeParams request =
    BackendTask.succeed (Response.render {})
head : RouteBuilder.App Data ActionData RouteParams -> List Head.Tag
head app =
    []
action :
    RouteParams
    -> Request
    -> BackendTask FatalError (Response ActionData ErrorPage)
action routeParams request =
    case request |> Request.formData formHandlers of
        Just ( response, parsedForm ) ->
            case parsedForm of
                Form.Valid (SignUp okForm) ->
                    BackendTask.Custom.run "createUser"
                        -- client-side validations run on the server, too,
                        -- so we know that the password and password-confirmation matched
                        (Encode.object
                            [ ( "username", Encode.string okForm.username )
                            , ( "password", Encode.string okForm.password )
                            ]
                        )
                        (Decode.succeed ())
                        |> BackendTask.allowFatal
                        |> BackendTask.map (\() -> Response.render { errors = response })
                Form.Invalid _ _ ->
                    "Error!"
                        |> Pages.Script.log
                        |> BackendTask.map (\() -> Response.render { errors = response })
        Nothing ->
            BackendTask.fail (FatalError.fromString "Expected form submission."
errorsView :
    Form.Errors String
    -> Validation.Field String parsed kind
    -> Html (PagesMsg Msg)
errorsView errors field =
    errors
        |> Form.errorsForField field
        |> List.map (\error -> Html.li [ Html.Attributes.style "color" "red" ] [ Html.text error ])
        |> Html.ul []
signUpForm : Form.HtmlForm String SignUpForm input Msg
signUpForm =
    (\username password passwordConfirmation ->
        { combine =
            Validation.succeed SignUpForm
                |> Validation.andMap username
                |> Validation.andMap
                    (Validation.map2
                        (\passwordValue passwordConfirmationValue ->
                            if passwordValue == passwordConfirmationValue then
                                Validation.succeed passwordValue
                            else
                                Validation.fail "Must match password" passwordConfirmation
                        )
                        password
                        passwordConfirmation
                        |> Validation.andThen identity
                    )
        , view =
            \formState ->
                let
                    fieldView label field =
                        Html.div []
                            [ Html.label
                                []
                                [ Html.text (label ++ " ")
                                , Form.FieldView.input [] field
                                , errorsView formState.errors field
                                ]
                            ]
                in
                [ fieldView "username" username
                , fieldView "Password" password
                , fieldView "Password Confirmation" passwordConfirmation
                , if formState.isTransitioning then
                    Html.button
                        [ Html.Attributes.disabled True ]
                        [ Html.text "Signing Up..." ]
                  else
                    Html.button [] [ Html.text "Sign Up" ]
                ]
        }
    )
        |> Form.init
        |> Form.hiddenKind ( "kind", "regular" ) "Expected kind."
        |> Form.field "username" (Field.text |> Field.required "Required")
        |> Form.field "password" (Field.text |> Field.password |> Field.required "Required")
        |> Form.field "password-confirmation" (Field.text |> Field.password |> Field.required "Required")
type Action
    = SignUp SignUpForm
type alias SignUpForm =
    { username : String, password : String }
formHandlers : Form.ServerForms String Action
formHandlers =
    Form.initCombined SignUp signUpForm