perf(core): fix query waterfalls — getTerm parallelism, hydrated terms reuse, menu pattern caching - #1409
Conversation
- getTerm() runs its usage-count and children queries under Promise.all (both depend only on the term row), saving a round trip on every tag/category archive page. - getMenu() request-caches the _emdash_collections url_pattern lookup, so pages rendering multiple menus only pay for it once per request. - Templates/demos no longer call getEntryTerms() per post detail page: getEmDashEntry already hydrates entry.data.terms (keyed by taxonomy name) via getAllTermsForEntries. The type generator now emits the hydrated terms field so templates can use it type-safely, and the checked-in emdash-env.d.ts files for the touched sites are updated to match. Query-count snapshots will change in CI (fewer queries per route). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 1893420 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | 1893420 | Jun 11 2026, 02:36 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | 1893420 | Jun 11 2026, 02:35 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | 1893420 | Jun 11 2026, 02:36 PM |
Scope checkThis PR changes 609 lines across 27 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR reduces request-time query waterfalls in packages/core by parallelizing independent taxonomy queries, reusing already-hydrated taxonomy terms in first-party templates/demos, and request-caching menu collection URL pattern lookups to avoid duplicated queries within a single render.
Changes:
- Run
getTerm()usage-count + children queries concurrently (Promise.all) to save a round trip. - Expose hydrated
entry.data.termsin generated collection types and update templates/demos/fixtures to use it instead of callinggetEntryTerms()per entry. - Request-cache
_emdash_collections.url_patternlookups during menu tree building so multiple menus in one request share the same query.
Reviewed changes
Copilot reviewed 17 out of 27 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| templates/starter/src/pages/posts/[slug].astro | Stop re-querying terms; use post.data.terms hydrated by getEmDashEntry. |
| templates/starter/emdash-env.d.ts | Regenerate types to include bylines? and terms?. |
| templates/starter-cloudflare/src/pages/posts/[slug].astro | Stop re-querying terms; use post.data.terms. |
| templates/starter-cloudflare/emdash-env.d.ts | Regenerate types to include bylines? and terms?. |
| templates/blog/src/pages/posts/[slug].astro | Replace getEntryTerms() call with hydrated post.data.terms?.tag. |
| templates/blog/emdash-env.d.ts | Regenerate types to include terms?. |
| templates/blog-cloudflare/src/pages/posts/[slug].astro | Replace getEntryTerms() call with hydrated post.data.terms?.tag. |
| templates/blog-cloudflare/emdash-env.d.ts | Regenerate types to include terms?. |
| packages/core/tests/unit/taxonomies/get-term.test.ts | New unit tests asserting getTerm() behavior + fixed 3-query budget. |
| packages/core/tests/unit/schema/zod-generator.test.ts | Assert generated TS includes hydrated bylines? and terms? fields. |
| packages/core/tests/unit/menus/menu-request-cache.test.ts | New unit tests verifying menu collection-pattern lookup is request-cached. |
| packages/core/src/taxonomies/index.ts | Parallelize getTerm() count + children queries via Promise.all. |
| packages/core/src/schema/zod-generator.ts | Emit terms?: Record<string, TaxonomyTerm[]> and import TaxonomyTerm in generated types. |
| packages/core/src/menus/index.ts | Request-cache collection url_pattern lookup used during menu URL resolution. |
| fixtures/perf-site/src/pages/posts/[slug].astro | Replace per-entry getEntryTerms() with hydrated post.data.terms?.tag. |
| fixtures/perf-site/emdash-env.d.ts | Regenerate types to include terms?. |
| demos/simple/src/pages/posts/[slug].astro | Replace per-entry getEntryTerms() with hydrated post.data.terms?.tag. |
| demos/simple/emdash-env.d.ts | Regenerate types to include terms?. |
| demos/preview/src/pages/posts/[slug].astro | Replace per-entry getEntryTerms() with hydrated post.data.terms?.tag. |
| demos/preview/emdash-env.d.ts | Regenerate types to include terms?. |
| demos/postgres/src/pages/posts/[slug].astro | Replace per-entry getEntryTerms() with hydrated post.data.terms?.tag. |
| demos/postgres/emdash-env.d.ts | Regenerate types to include terms?. |
| demos/playground/src/pages/posts/[slug].astro | Replace per-entry getEntryTerms() with hydrated post.data.terms?.tag. |
| demos/playground/emdash-env.d.ts | Regenerate types to include terms?. |
| demos/cloudflare/src/pages/posts/[slug].astro | Replace per-entry getEntryTerms() with hydrated post.data.terms?.tag. |
| demos/cloudflare/emdash-env.d.ts | Regenerate types to include terms?. |
| .changeset/lucky-pots-repair.md | Publish notes describing the three query-waterfall fixes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
What does this PR do?
Three query-waterfall fixes found while investigating uncached-request latency (production pages show 5–7 fully serialized queries at 15–40ms each on D1):
getTerm()parallelization — the usage-count and children queries both depend only on the already-fetched term row but ran sequentially. They now run underPromise.all: one round trip saved on every tag/category archive page. Locale filtering on the children query is unchanged (content_taxonomiesis keyed by translation_group and has no locale column, so the count query has no locale concern).Reuse hydrated terms in templates —
getEmDashEntry/getEmDashCollectionalready hydrateentry.data.terms(same join, collection and locale filtering asgetEntryTerms), but 10 first-party templates/demos re-queried terms per entry anyway. They now readpost.data.terms?.tag ?? []directly — one query saved per post-detail page (two in the starter, which refetched tag and category). Supporting change: the type generator (zod-generator.ts) now emitsterms?: Record<string, TaxonomyTerm[]>alongside the existingbylinesfield (additive), and the touched sites' checked-inemdash-env.d.tsfiles are regenerated. Note: hydrated terms are label-sorted, so tag display order may change where the old per-entry query used a different order.Menu collection-pattern lookup request-cached —
buildMenuTreequeried_emdash_collections.url_patternpergetMenu()call; a page rendering header + footer menus paid it twice. The lookup is now wrapped inrequestCachedkeyed by the sorted slug set.Query-count snapshots will change (decreases only) — intentionally not regenerated here; CI auto-updates them on the PR. Please review that the snapshot diff shows only decreases.
Closes #
Type of change
Checklist
pnpm typecheckpasses (packages, templates, demos, fixtures)pnpm lintpassespnpm testpasses (or targeted tests for my change) — unit 175 files / 2,640 tests; integration 70 files / 1,039 testspnpm formathas been runAI-generated code disclosure
Screenshots / test output
New tests (TDD):
get-term.test.ts(dialect-agnostic viadescribeEachDialect; behavior lock + exact 3-query budget via a counting Kysely plugin),menu-request-cache.test.ts(two getMenu calls → one url_pattern query; failed at 2 queries before the fix), zod-generatortermsemission. Known pre-existing failure unrelated to this PR:demos/previewastro checkfails on duplicated astro versions in node_modules (identical on main).🤖 Generated with Claude Code
Try this PR
Open a fresh playground →
A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.
Tracks
perf/query-waterfalls. Updated automatically when the playground redeploys.