ADR-0022 · Import Map Stays After <router-slot>
Status: Accepted
The default shell emits <router-slot> before <script type="importmap"> and the app script, not after. Per spec, an import map only affects module scripts that haven't started fetching yet — so an inline <script type="module"> written directly in page content can't resolve bare specifiers through the import map; it needs a literal relative or absolute URL instead. This is the deliberate ordering, not an oversight.
Why
An inline <script type="importmap"> blocks HTML parsing at the point it's encountered. Positioned at the end of <body>, that pause happens after all SSR content has already reached the parser and can paint — consistent with "SSR-fast first paint, then live." Moving it into <head> would front-load that pause on every request, in every mode that emits JS, to fix a scenario that's both narrow and already discouraged: ADR-0009 already establishes that inline <script> tags aren't the sanctioned extension point — widgets and custom elements are, and they load via import() from app.js, whose own <script> tag is already positioned after the import map, so widget loading is unaffected.
The benefit is also uneven across modes: leaf has no client router to benefit from earlier import-map arrival, only ships an empty <router-slot> so ordering is moot, and none never emits an import map at all. Only root has any argument for it, and even there SSR content should paint before the router boots.
Escape hatch
Consumers who need bare specifiers in inline page scripts can supply a custom index.html and place the import map in <head> themselves — which forfeits automatic importmap.json merging. The sanctioned path stays a widget or custom element, which always sees the import map already registered.