How the pieces fit
Follow the startup path
The application built in Learn now has five pages, one service, and tests. Read it from the process toward the browser:
Mill compiles Scala and includes ordinary classpath resources.
Mainloads the transport configuration, the LiveView clients, static assets, and application services.Mainpairs typed routes with LiveViews and starts ZIO HTTP.A route selects a LiveView, and its layouts wrap the rendered page.
The browser loads
app.js, connects, forwards events, and applies updates.
Understand both mounts
A Live route mounts once to produce the initial HTTP response and again when the browser establishes its live connection. These mounts create independent models; the HTTP model is not passed into the connected lifecycle. The lifecycle page explains the consequences for state, resources, failures, and reconnects.
Separate the application boundaries
Main.scala is the process boundary. It owns configuration, resource loading,
dependency layers, route composition, and server lifetime. Per-visitor state and
HTML rendering do not belong there.
Routes.scala is the URL boundary. Routes begin with
liveval live: LiveRouteSeed[Unit]The root path /, from which routes are declared, as in
live / "counter" -> CounterLiveView()., decode path or query data when needed,
and are paired with LiveViewstrait LiveView[Msg, Model]Defines a server-rendered page with typed messages and model state. by
Live.routerval router: LiveRouterStarts an application, as in
Live.router.withRootLayout(RootLayout())(live -> HomeLiveView()).. Named route values also
give navigation code one typed source of URLs.
RootLayout.scala is the document boundary. A
LiveRootLayouttrait LiveRootLayout[-A, -Ctx]Renders the outer HTML document: <html>, <head>, scripts, stylesheets, and <body>. renders the outer
<html>, <head>, and <body>. It owns global scripts, stylesheets, metadata,
and the fallback title. When served through ZioHttp.routes with validated
configuration, Scalive injects the browser-bound CSRF meta token into its
<head>.
Each LiveView, such as CounterLiveView.scala, is an interactive-page
boundary. mountdef mount(ctx: MountContext): zio.package.Task[Model]Builds the initial model. creates
connection-local state,
handleMessagedef handleMessage(model: Model, ctx: MessageContext): Msg => zio.package.Task[Model]Handles a message against the current immutable model. changes state,
and viewdef view(model: Signal[Model]): HtmlElement[Msg]Describes the HTML for every model this lifecycle will have. describes typed HTML and
event bindings. A LiveView does not start the HTTP server or locate its own
asset files.
Services, such as Thermostat.scala, are the data boundary. They are built
once by a layer in Main and passed to the LiveViews that need them.
resources/public/app.js is the browser boundary. It creates LiveSocket,
passes the server-issued CSRF token, and connects to the socket path. Add hooks
here only for behavior that requires browser APIs. Application state and
ordinary event handling stay in Scala.
LiveViewClientAssetsclass LiveViewClientAssetsThe supported Phoenix and Phoenix LiveView browser clients. serves
the Phoenix clients packaged with Scalive, and
StaticAssetsclass StaticAssetsA loaded asset description with URL helpers, HTML tag helpers, and HTTP routes. serves the files under
resources/public at versioned URLs. The
client setup guide covers custom
browser builds.
Keep dependencies pointing inward
The process constructs infrastructure and passes dependencies to routed LiveViews. A growing application normally follows this direction:
Main
├── LiveViewClientAssets
├── StaticAssets
├── ZioHttpConfig
├── application services
└── Live.router
├── typed routes
├── root and live layouts
└── LiveViews
├── immutable models
├── typed messages
└── rendered HTML
Browser
└── LiveSocket
├── DOM events to the server
└── server patches to the DOMLiveViews may use injected services, but services should not depend on a particular LiveView or browser connection. This keeps durable domain behavior separate from connection-local presentation state.
Grow the project
Keep these boundaries as the application expands:
Add route values before wiring new LiveViews into the router.
Use a
LiveLayouttrait LiveLayout[-A, -Ctx]Wraps a LiveView's content inside the document, for example in shared navigation. for shared markup inside the document; reserveLiveRootLayoutfor the complete shell. The layouts guide adds a navigation bar.Build ZIO layers at startup for services required by routed LiveViews.
Add browser packages and hooks only for browser-side behavior.
Keep durable state in services or storage rather than a LiveView model.
Where to go next maps the next parts of an application to guides and examples.