Best viewed on desktop Mobile Friendly tutorial coming soon!

Loading on Demand

The facade in the last lesson defers a component’s JavaScript — but its HTML still renders on the server, so the widget is on the page from the first byte; only its interactivity waits. Sometimes that isn’t what you want. Sometimes you want to defer the whole component — render nothing at all until the user asks for it (opens a panel, expands a section, clicks a tab).

That’s a different axis. Here’s the whole picture:

ApproachThe contentIts JS bundleDeferred in the dev preview?
Facade / load: (last lesson)server-rendered, there immediatelysplit outno — dev loads it eagerly
<if> + a lazy componentnot rendered until shownsplit outyes
dynamic import()not rendered until shownsplit outyes

The facade keeps the content and defers hydration; the other two defer the content itself. This lesson does the second kind — and unlike the facade, you’ll actually see it happen.

Gate a lazy component with <if> (your job)

src/routes/+page.marko renders <DetailsPanel/> directly. It’s already a lazy import — with { load: "render" } puts its JavaScript in its own bundle — but because it’s always rendered, it’s server-rendered into every page anyway. Put it behind a button so it appears only on demand:

<let/show=false>
<button onClick() { show = true }>Show details</button>
<if=(show)>
<DetailsPanel/>
</if>

Now the panel isn’t in the page at all until you click — its HTML and its bundle both arrive on demand. Reload and watch: the panel is gone, just the button; click it and the panel renders in. That’s a change you can actually see — the thing the facade lesson couldn’t show you, because there the content was there all along.

The same thing by hand: dynamic import()

<if> plus a lazy tag is the declarative route. The imperative equivalent is a plain dynamic import() — you fetch the component’s module yourself in the handler, then render it with Marko’s dynamic-tag syntax, <${...}/>:

<let/Panel=null>
<button onClick() {
import("../tags/details-panel.marko").then((m) => { Panel = m.default });
}>Show details</button>
<if=(Panel)>
<${Panel}/>
</if>

Same result — nothing until the click, then the panel loads and renders, and it’s split into its own chunk either way. These are two styles, not a right and a wrong. The declarative version is less code and lets Marko name and wire the chunk for you; the imperative version is ordinary JavaScript you’d recognize from any framework, and it hands you the module object directly, which is handy if you need to do something with it before it renders.

That’s the on-demand toolbox: reach for a facade when you want the content shown and only its hydration deferred, and reach for <if> + a lazy component (or a dynamic import()) when you don’t want to render it at all until the user asks.

Powered by WebContainers Built with TutorialKit Built with ❤️ by defunkt-dev
Files
Preparing Environment
  • Installing dependencies
  • Starting dev server