Flutter SEO · one tree · two renderers

Flutter UI. Searchable HTML.

Visitors get the page immediately — real text, real headings, real links, the page a search engine indexes — and Flutter takes it over once it is ready, without a blank screen. One tree in Dart, drawn twice: there is no second UI to keep in step.
Unibox
↓

HTML and CSS

the page a search engine reads: real text, real headings, real links
↓

Flutter

the same tree as widgets — a document until you touch it, then the application draws it. The handover is live in this frame

Make Flutter Web pages indexable

Flutter Web paints into a canvas, so a search engine is served an empty page: no text, no heading, no link to follow. Unibox writes the same tree as markup, so the page a crawler reads is the page a visitor lands on — and the app that takes it over is the one you already have.
the screen, in Dart
Box(
children: <UINode>[
TextNode(
'Apartments in Amsterdam',
headingLevel: 1,
),
Box(
url: 'apartments/amsterdam',
children: <UINode>[TextNode('See all')],
),
],
)
what a crawler is served
<body>
<div class="unibox-box-...">
<h1>Apartments in Amsterdam</h1>
<a href="apartments/amsterdam">
See all
</a>
</div>
</body>
class names elided
Flutter still takes the page overThe build is prefetched while the reader reads and boots into the page it was served, at the scroll and with the presses they already made. From there the dialogs, the forms and the screens behind the list are ordinary Flutter.
Adopted one page at a timeDescribe only the pages that have to be found; the rest of the application stays the Flutter it already is. Unibox makes the rendering crawlable and keeps the UI in one place — urls, copy, metadata, a sitemap and the deployment remain the application's.

Just boxes

A screen is containers: a box holds text, a picture, a field, or more boxes, and there is no widget or element to learn beside it. The four nodes this page had not drawn yet are the parts a card is made of — a field with no chrome, a stack, a picture, and a child placed at an offset. The list beside the card is that card, taken apart.
A living room with tall windows and a staircase
€ 1 850
Over the rooftops, two rooms72 m² · Paris · available now
the card, node by node
  • Box
    crossAlign, gap
  • TextInputNode
    placeholder
  • Box
    radius, overflow
  • StackNode
    height
  • ImageNode
    src, alt, fit
  • PositionedNode
    top, right
  • Box
    radius, backgroundColor
  • TextNode
    labelLarge
  • Box
    padding, gap
  • TextNode
    titleMedium
  • TextNode
    bodySmall
the same tree, two calls
node.toWidget()
Flutter widgets
node.toHtml()
an HTML string
The hero, the catalogue and this bar are the same nodes, drawn by the same two calls.

Material

Buttons

Four shapes around one ink well: press one.
Filled
Tonal
Outlined
Text

Chips

A choice, and one with a cross.
Two rooms
Studio
Map
Sold
Garden
Balcony

Segments

A set of choices in one pill.
Day
Week
Month
List
Grid
Map

Text fields

The filled field, with its own focus, label and error.
We only use it for the receipt.
Email
We only use it for the receipt.

Cards and tiles

All six heights on the surface Material tints to match, the two other shapes a card comes in, and a row that says who it is.
Level 0surfaceContainerLowest · no shadow
Level 1surfaceContainerLow
Level 2surfaceContainer
Level 3surfaceContainerHigh
Level 4surfaceContainerHighest
Level 5surfaceContainerHighest · shadow only
Outlinedsurface · outlineVariant
Confirm
FilledsurfaceContainerHighest
AL
Ada Lovelace
ada@example.com
2m
Charles Babbage
charles@example.com
1h

Navigation bar

Three destinations; the chosen one sits on a pill.

Tabs

A tab bar over three pages: the bar says which tab is chosen and the node draws that page.
Six flats, from the same tree.
A map beside them on a wide screen.
What the tenants said.

Secondary tabs

The same three tabs packed, each as wide as its label: the underline is the label's own width, not a share of the bar.
Six flats, from the same tree.

Pages

A pager: `PagerNode` settles on one child at a time, and its children are the size the tree states. Swipe it sideways.
One
Two
Three

Scaffold

A screen: `MaterialScaffold` holds a bar, a body and a bottom bar, and only the body scrolls — the bars state a container and the page a surface, so a pull past the list opens inside the body and never at a bar’s edge. The second screen’s bar gives its height back instead: the collapse is a number a store hands the rebuild, which a document never runs.
An app bar that stays
Apartments
24
Garden loft
€1 280
Two rooms by the park
€1 460
Studio, sixth floor
€ 920
Attic with a terrace
€1 700
One room in the old town
€ 860
House on the canal
€2 100
Loft over the bakery
€1 180
Chambre de bonne
€ 640
An app bar that gives its height back
Saved
Garden loft
€1 280
Two rooms by the park
€1 460
Studio, sixth floor
€ 920
Attic with a terrace
€1 700
One room in the old town
€ 860
House on the canal
€2 100
Loft over the bakery
€1 180
Chambre de bonne
€ 640

App bars

The bar a page opens with, and the one a list scrolls under: `MaterialAppBar` states the height its slot keeps, the container it paints, and the `pin` that makes it stay.
A title, and a node at the end
Shortlist
12
A glyph before the title
Results
6 of 24
The large one, with air above its headline
Garden loft

Dialog

An OverlayNode: in the tree only while it is open.
Delete listing
Title, content and actions, on the surface a dialog is raised by. The button is a MaterialInkWell over a filled box, so a press leaves ink; the dialog is in the tree only while it is open.

Type scale

Display large
Sphinx of black quartz
Display medium
Sphinx of black quartz
Display small
Sphinx of black quartz
Headline large
Sphinx of black quartz
Headline medium
Sphinx of black quartz
Headline small
Sphinx of black quartz
Title large
Sphinx of black quartz
Title medium
Sphinx of black quartz
Title small
Sphinx of black quartz
Body large
Sphinx of black quartz
Body medium
Sphinx of black quartz
Body small
Sphinx of black quartz
Label large
Sphinx of black quartz
Label medium
Sphinx of black quartz
Label small
Sphinx of black quartz

Colour roles

primary#8C4A5D
onPrimary#FFFFFF
primaryContainer#FFD9E1
onPrimaryContainer#703346
secondary#75565D
onSecondary#FFFFFF
secondaryContainer#FFD9E1
onSecondaryContainer#5B3F46
surface#FFF8F8
onSurface#22191B
onSurfaceVariant#514346
surfaceContainerLowest#FFFFFF
surfaceContainerLow#FFF0F2
surfaceContainer#FBEAEC
surfaceContainerHigh#F5E4E6
surfaceContainerHighest#EFDFE1
outline#847376
outlineVariant#D6C2C5
error#BA1A1A
onError#FFFFFF
scrim#000000

Shapes

A path in a view box: a hexagon, a circle, and a ring — and that ring twice over, turning: a progress indicator and the spinner beside it.
6
Ada