Skip to content

feat(layer): port first docus upstream bundle#27

Merged
amondnet merged 3 commits into
mainfrom
feat/docus-port-bundle-1
May 28, 2026
Merged

feat(layer): port first docus upstream bundle#27
amondnet merged 3 commits into
mainfrom
feat/docus-port-bundle-1

Conversation

@amondnet
Copy link
Copy Markdown
Contributor

Summary

First bundle of ports from ref/docus upstream (v5.4.1 → v5.11.0+main). Picks five framework-agnostic changes that don't depend on @nuxt/ui or @nuxtjs/i18n, so they apply cleanly to our shadcn-vue + Tailwind v4 layer.

Survey: docs/docus-upstream-changes.md

Changes

# Area File Upstream commit
1 inferSiteURL env var coverage + withHttps packages/layer/utils/meta.ts — (gradual upstream improvements)
2 Register /raw/*.md prerender path packages/layer/app/pages/[...slug].vue 9ceafe6f
3 content.config auto-detect + utils/pages.ts packages/layer/content.config.ts, packages/layer/utils/pages.ts f9e999d8, 99c78508
4 transformNavigation + findPageBreadcrumbs packages/layer/app/utils/navigation.ts (used across docus)
5 sitemap.xml route packages/layer/server/routes/sitemap.xml.ts 45bffbc5, cd2c62e4

Behavioural notes

  • landing collection becomes opt-out: if the consuming app ships app/pages/index.vue, the layer skips defining a landing collection. Existing apps with only a content/index.md are unaffected.
  • /docs auto-prefix: if content/docs/ exists, the docs collection sources from there and uses /docs as the prefix. Flat content/ layouts continue to work as before.
  • Sitemap route is opt-in by URL: hit /sitemap.xml to render; nothing else references it yet.

Verification

  • bun lint — clean (only an unrelated baseline-browser-mapping data warning)
  • cd packages/layer && bun typecheck — no new errors (pre-existing errors in AppHeaderLogo, AppSearch, ComponentsList, DialogScrollContent, modules/config.ts, nuxt.config.ts, pages/[...slug].vue TOC code remain; none touched here)

Not included (deferred to follow-ups)

Per the survey, these need additional dependencies or stack work:

  • MCP server + Skills + Vercel markdown-rewrite (AI/agent bundle)
  • useSeo JSON-LD composable (hreflang requires @nuxtjs/i18n)
  • d shortcut for color mode (needs vueuse-based reimplementation)

Port five framework-agnostic changes from docus upstream that don't
depend on @nuxt/ui or @nuxtjs/i18n. See docs/docus-upstream-changes.md
for the full survey.

- inferSiteURL: detect VERCEL_PROJECT_PRODUCTION_URL / VERCEL_BRANCH_URL
  / VERCEL_URL / NUXT_PUBLIC_SITE_URL and normalize with ufo.withHttps
- pages/[...slug]: register /raw/*.md prerender path so static builds
  expose markdown variants for AI agents (docus 9ceafe6f)
- content.config: skip landing collection when app/pages/index.vue
  exists; auto-prefix /docs when content/docs/ exists (docus f9e999d8,
  99c78508). New helpers in utils/pages.ts (landingPageExists,
  docsFolderExists).
- app/utils/navigation: add transformNavigation + findPageBreadcrumbs
  with BreadcrumbItem type
- server/routes/sitemap.xml: serve a sitemap derived from content
  collections, skipping .navigation paths and sitemap:false pages
  (docus 45bffbc5, cd2c62e4)
@amondnet amondnet self-assigned this May 28, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several enhancements to the layer, including new navigation utilities, dynamic collection definitions in content.config.ts based on the existence of a landing page or docs folder, a dynamic XML sitemap generator route, and improved site URL inference. The review highlights three critical issues: first, joinURL from ufo is used for filesystem path operations, which can fail on Windows and should be replaced with standard path-joining utilities; second, the sitemap generator queries sitemap and modifiedAt fields which are missing from the docsSchema definition; and third, the sitemap generator attempts to query localized collections that are not registered in the content configuration.

Comment thread packages/layer/utils/pages.ts
Comment thread packages/layer/server/routes/sitemap.xml.ts
Comment on lines +63 to +75
const availableLocales = getAvailableLocales(config.public as Record<string, unknown>)
const collections: string[] = availableLocales.length > 0
? availableLocales.map(locale => `docs_${locale}`)
: ['docs']

if (availableLocales.length > 0) {
for (const locale of availableLocales) {
collections.push(`landing_${locale}`)
}
}
else {
collections.push('landing')
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When availableLocales is not empty, this route attempts to query localized collections like docs_en or landing_en. However, packages/layer/content.config.ts only defines the base docs and landing collections and does not dynamically register localized collections based on the configured locales.

As a result, queryCollection will throw an error for these localized collections, which is caught and silently ignored, leading to an empty sitemap on localized sites.

To resolve this, you should dynamically define the localized collections in packages/layer/content.config.ts by reading the configured locales from the Nuxt options, or ensure they are registered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[claude] ⏭️ Not applied in this PR

  • Reason: Dynamic i18n collection registration in content.config.ts is explicitly scoped to a follow-up per the survey doc (docs/docus-upstream-changes.md) — @nuxtjs/i18n isn't installed yet and the layer has no i18n active today, so availableLocales is always empty here and the localized branch never executes.
  • Alternative: The try/catch around queryCollection already swallows missing-collection errors. I updated the comment to mention "or i18n locale collections are not registered yet" so the intent is documented.

Thanks @gemini-code-assist — this will become valid once we land the i18n port.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the clarification, @amondnet. That makes perfect sense. Given that the i18n infrastructure is currently out of scope for this PR and the try/catch block safely handles the missing collections, the current implementation is acceptable. I appreciate you documenting the intent in the comments.

@amondnet
Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai Bot commented May 28, 2026

@cubic-dev-ai review

@amondnet I have started the AI code review. It will take a few minutes to complete.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 28, 2026

Deploying docs-please with  Cloudflare Pages  Cloudflare Pages

Latest commit: 97263fe
Status:🚫  Deploy failed.

View logs

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files

Architecture diagram
sequenceDiagram
    participant Dev as Developer
    participant App as Nuxt App
    participant Layer as Docus Layer
    participant FS as File System
    participant Content as Nuxt Content
    participant Nav as Navigation Utils
    participant DB as In-Memory Store
    participant SSG as Static Generator
    participant Client as Browser/Agent

    Note over Dev,Client: Build-time Configuration Phase

    Dev->>App: Start dev/build
    App->>Layer: Initialize layer (content.config.ts)
    Layer->>FS: Check landing page exists
    FS-->>Layer: landingPageExists()
    alt Landing page exists
        Layer->>Layer: Skip landing collection
    else No landing page
        Layer->>Content: Define landing collection
    end
    Layer->>FS: Check docs folder exists
    FS-->>Layer: docsFolderExists()
    alt Docs folder exists
        Layer->>Content: Define docs collection with /docs prefix
    else Flat content
        Layer->>Content: Define docs collection with / prefix
    end

    Note over Dev,Client: Runtime Request Phase

    Client->>App: Request page
    App->>Layer: Load page component
    Layer->>Content: Query content collections
    Content-->>Layer: Page data
    alt Server-side prerender
        Layer->>SSG: prerenderRoutes("/raw/*.md")
        SSG-->>Client: Static .md file
    end

    Client->>App: Request /sitemap.xml
    App->>Layer: sitemap.xml.ts handler
    Layer->>Layer: inferSiteURL() with env vars
    alt NUXT_PUBLIC_SITE_URL set
        Layer->>Layer: Use as-is
    else VERCEL_URL set
        Layer->>Layer: withHttps() applied
    end
    Layer->>Content: Query all collections
    Content-->>Layer: Pages with path, modifiedAt
    Layer->>Layer: Filter navigation paths & excluded
    Layer->>Layer: Build XML with escapeXml()
    Layer-->>Client: XML sitemap response

    Client->>App: Navigate to /docs/guide
    App->>Nav: transformNavigation()
    Nav->>Nav: Strip locale/docs level
    alt i18n enabled
        Nav->>Nav: Find locale-specific navigation
    else No i18n
        Nav->>Nav: Find /docs children
    end
    Nav-->>App: Clean navigation tree
    App->>Nav: findPageBreadcrumbs(navigation, path)
    Nav->>Nav: Walk tree recursively
    Nav-->>App: Breadcrumb trail
    App-->>Client: Rendered page with navigation & breadcrumbs

    opt Sitemap generation failure
        Layer->>Layer: Catch collection not found (skip silently)
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/layer/server/routes/sitemap.xml.ts Outdated
amondnet added 2 commits May 28, 2026 19:57
- utils/pages.ts: use node:path.join instead of ufo.joinURL for
  filesystem paths (cross-platform; @gemini-code-assist)
- content.config.ts: declare optional 'sitemap' field on docs schema so
  frontmatter sitemap:false actually opts pages out (@gemini-code-assist)
- server/routes/sitemap.xml.ts: drop modifiedAt handling (not in
  schema, would be undefined); return 404 when site URL is missing so we
  never emit relative <loc> entries (@cubic-dev-ai)
Two issues surfaced in the Cloudflare Pages deploy of #27:

1. Crawler-discovered section index routes (e.g. /docs/getting-started)
   render via [...slug].vue even though no source markdown exists. The
   unconditional addPrerender call I added in PR #27 then registered
   /raw/docs/getting-started.md, which the raw handler 404s, failing the
   build with 'fatal: Page not found'. Gate the registration on
   page.value so we only enqueue raw paths for pages that actually have
   a source file.

2. The new sitemap.xml route imported inferSiteURL from utils/meta,
   which also exports getPackageJsonMetadata (node:fs/promises +
   node:path). That pulled node:* modules into the Worker bundle and
   broke the Cloudflare publish step ('No such module node:buffer').
   Split inferSiteURL out into a runtime-safe utils/site-url module and
   re-export it from utils/meta so existing build-time callers keep
   working.
@sonarqubecloud
Copy link
Copy Markdown

@amondnet amondnet merged commit 5759e4c into main May 28, 2026
2 of 3 checks passed
amondnet added a commit that referenced this pull request May 28, 2026
* feat(seo): add useSeo composable with JSON-LD and canonical

Ports upstream docus commit d283f9aa (feat(layer): add more seo optimization)
as item #10 from docs/docus-upstream-changes.md.

The new useSeo composable in packages/layer/app/composables/useSeo.ts:
- Wires reactive title/description through useSeoMeta with OpenGraph +
  Twitter card meta and an og:url that matches the canonical link
- Resolves the canonical URL from useAppConfig().docs.url (populated by
  modules/config.ts) with an inferSiteURL() fallback
- Emits Article or WebSite JSON-LD plus a BreadcrumbList script when the
  caller supplies a breadcrumb trail
- Defers hreflang emission until @nuxtjs/i18n is wired into the layer
  (TODO comment marks the upstream branch)

packages/layer/app/pages/[...slug].vue now uses useSeo with a breadcrumb
trail computed from useNavigation + findPageBreadcrumbs (landed in PR #27).

* fix(seo): apply review suggestions

- Use textContent instead of innerHTML for JSON-LD script tags
  (XSS-safe per Unhead recommendation; cubic + gemini)
- Allow BreadcrumbList schema to render even when title is unset
  by scoping the title guard to Article/WebSite blocks only
  (cubic + gemini)
@amondnet amondnet deleted the feat/docus-port-bundle-1 branch May 28, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant