In Basics we showed how to run a simple application:
main :: IO ()
main = do
  run 3000 $ liveApp quickStartDocument (runPage hello)
Let's go over the arguments of liveApp, starting with the Document function
liveApp
  :: (BL.ByteString -> BL.ByteString)
  -> Eff '[Hyperbole, Concurrent, IOE] Response
  -> Wai.Application
liveApp = _
The first argument is a document function. This turns an initial page fragment into a full document, complete with <script> and <link> tags.
The quickStartDocument adds some required CSS and Javascript, including live reload. But you can customize it however you wish. For example, here's how you might set your own title and add some global css
documentHead :: View DocumentHead ()
documentHead = do
  title "Best Website Ever"
  mobileFriendly
  style cssEmbed
  script' scriptEmbed
  stylesheet "/mysite.css"

main :: IO ()
main = do
  run 3000 $ liveApp (document documentHead) (runPage hello)
The second argument of liveApp is an Effect monad which returns a Response. We will rarely return a Response directly, so we use the runPage function to turn Page es '[...] into an Eff es Response
The idea is that an application is divided into independent Pages, which completely reload when you navigate between them. This is a deliberate choice to simplify development. In Single Page Applications, internal navigation often causes all sorts of strange state synchronization issues.
We suggest you create a module for each Page, each with its own page function
page :: Page es [Message, Counter]
page = do
  pure $ do
    hyper Message1 $ messageView "Hello"
    hyper Message2 $ messageView "World"
    hyper Counter $ viewCount 0
Since we have more than one Page, we need a way to choose between them. You could create a manual function Hyperbole :> es => Eff es Response which reads the Request, and runPage different Pages depending on the Path, but Hyperbole comes with support for type-safe Routes:
data AppRoute
  = Main
  | Messages
  | User UserId
  deriving (Eq, Generic)

instance Route AppRoute where
  baseRoute = Just Main
>>> routeUri Main
"/"

>>> routeUri (User 3)
"/user/3"
Then, as the second argument to liveApp, you can pattern match on the Route to run various Pages using routeRequest. If your route has data in it, you can pass it to the corresponding page
router :: (Hyperbole :> es) => AppRoute -> Eff es Response
router Messages = runPage Messages.page
router (User cid) = runPage $ Users.page cid
router Main = do
  pure $ view $ do
    el "click a link below to visit a page"
    route Messages "Messages"
    route (User 1) "User 1"
    route (User 2) "User 2"

app :: Application
app = liveApp (document documentHead) (routeRequest router)
Your application will want to support various side effects. It's helpful to create a single function that runs all shared effects:
runApp :: (Hyperbole :> es, IOE :> es) => AppConfig -> Eff (Reader AppConfig : Concurrent : es) a -> Eff es a
runApp config = runConcurrent . runReader config
Add all your effects by calling your runner:
app' :: AppConfig -> Application
app' config = liveApp (document documentHead) (runApp config $ routeRequest router')
A few pages might use effects that the rest don't need, or a specific implementation. You can choose to run an effect for a single page
router' :: (Hyperbole :> es, Concurrent :> es) => AppRoute -> Eff es Response
router' Messages = runReader @Text "Secret Message!" $ runPage SideEffects.page
router' (User cid) = runPage $ Users.page cid
router' Main = pure $ view "..."