Platform
HTML & prerendering
miso's View type doubles as an HTML serialiser via the ToHtml class in Miso.Html.Render. Build a View with the normal DSL and render it to a lazy ByteString on the server — or, as this website does, at build time.
class ToHtml a where
toHtml :: a -> L.ByteString
pageHtml :: L.ByteString
pageHtml = toHtml $ H.div_ [ HP.id_ "root" ] [ "Hello, world!" ]Instances are provided for View and [View]. Servant users can serve them directly with servant-miso-html, which provides an HTML content type for View and Component values:
import Servant.Miso.Html (HTML)
type Home = "home" :> Get '[HTML] (Component context props model action)
type About = "about" :> Get '[HTML] (View context model action)
type Contact = "contact" :> Get '[HTML] [View context model action]Prerendering
Prerendering is delivering HTML from a web server (or a static host) before the client loads and draws anything. It comes in two flavours: static prerendering assumes no model state needs to be shared between server and client; dynamic prerendering uses hydrateModel to share it.
Static prerendering
miso provides prerender and miso for static prerendering. Any page can be generated from a View with toHtml; on the client, pass the matching component to miso (instead of startApp) so it hydrates the markup rather than redrawing:
main :: IO ()
main = prerender defaultEvents $
(component () noop $ \_ _ () -> "hello world") { logLevel = DebugPrerender }With the payload and HTML delivered together, the console shows:
[DEBUG_HYDRATE] Successfully prerendered pageDynamic prerendering
Dynamic prerendering shares model state so the client hydrates from a meaningful initial state rather than a blank model. The -fssr flag must be enabled when compiling the server. The hydrateModel field is Maybe (IO model): when set, the action runs once at hydration time to produce the initial model; a typical pattern embeds the model as JSON in the response and reads it back through the JS DSL:
myComp :: App Model Action
myComp = (component defaultModel updateModel viewModel)
{ hydrateModel = Just $ do
val <- jsg "window" ! "__initialModel__"
fromJSValUnchecked val
}
-- On the server, populate window.__initialModel__ alongside the rendered HTML:
serverView :: context -> props -> Model -> View context Model Action
serverView _ _ m =
H.div_ []
[ H.script_ [] ("window.__initialModel__ = " <> encode m)
, appView m
]When hydrateModel is Nothing the static model field is used instead — equivalent to static prerendering.
How this site does it
haskell-miso.org has no server. A small prerender executable, compiled with vanilla GHC and -fssr, walks every route, calls toHtml on the root component and writes index.html files into public/. The WASM bundle then hydrates whichever page was loaded with misoWithContext and takes over navigation with Miso.Router.