Skip to content

Quarto render time scales ~quadratically with API page count due to the generated per-object sidebar (19.5 min → 31 s for a 90-page site) #261

Description

@wolf

Summary

For packages with non-trivial APIs, great-docs build spends nearly all of its time in the
Quarto render phase, and that time scales roughly quadratically with the number of API pages.
The cause is the generated _quarto.yml sidebar: one entry per documented object, as a bare
file path. Quarto re-resolves the full sidebar for every page it renders (apparently reading
each target file's metadata to get its title), so an API with N objects pays ~N² metadata reads.

On a real package (86 API objects, 90 pages total, zero executable code cells), the
measured difference is 19m 29s as shipped vs 30.8s with the sidebar removed — everything else
identical.

This is distinct from #172, which covered generation-phase costs (introspection, source-link
generation). Here generation is fast; it's the quarto render step that blows up.

Measurements

All runs on the same 90-page site (macOS, Quarto 1.9.38, great-docs 0.14.0). The site's API
pages contain no executable cells — pure signature/docstring pages.

Configuration quarto render time
As generated by great-docs 19m 29s
As generated, minus the jupyter: python3 line 18m 41s
Same 90 pages, bare project: type: website 14.6s
+ the great-docs SCSS theme (9,167 lines) 15.1s
+ the three Lua filters (output-title, details, gd-lightbox) 15.6s
Full generated config, render list cut to 10 pages 4.3s (~0.4s/page)
Full generated config, sidebar block deleted 30.8s
Full generated config, sidebar entries rewritten to explicit text:/href: 6m 07s

Two observations from the table:

  1. The theme, filters, and engine binding are all essentially free. The sidebar accounts for
    ~97% of the render time.
  2. Per-page cost is not constant: ~0.4s/page when 10 pages render, ~12.4s/page when 90 render.
    Cost grows with (pages rendered × sidebar entries), i.e. quadratically in API size, so this
    gets dramatically worse for larger packages.

Suggested fixes

  1. Emit sidebar entries as explicit text: + href: pairs instead of bare paths.
    great-docs already knows every page's title at generation time. Measured effect: 3.1×
    (19.5m → 6m). Low-risk, no UX change.
  2. Offer a compact-sidebar option (sections link to per-section index pages instead of
    listing every method). Measured upper bound: ~36× (19.5m → ~31s). Changes navigation UX,
    so presumably opt-in.

Even with explicit titles Quarto still pays substantial per-page × per-entry cost (6m is much
better than 19.5m but far from 31s), so the residual may be worth raising with quarto-cli —
happy to file there too if useful.

Side note

jupyter: python3 is written into _quarto.yml unconditionally, binding every page to the
Jupyter engine even when no page in the site has executable cells. At project scale it's minor
(48s here), but it adds ~20s of kernel startup to every single-page render, which hurts
iterating on one page. Only writing the key when executable content exists (or letting users
set jupyter: false) would avoid it.

Environment

  • great-docs 0.14.0
  • Quarto 1.9.38
  • macOS (Darwin 25.5), Python 3.13
  • Package: internal library, 86 documented objects / 90 site pages, no executable code cells

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions