Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/base-aware-starter-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cloudflare/nimbus-docs": patch
"@cloudflare/create-nimbus-docs": patch
---

Keep framework assets and starter navigation working when an Astro site is deployed under a sub-path.
4 changes: 2 additions & 2 deletions packages/nimbus-docs/src/components/NimbusHead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ const ogType = isHomePage ? "website" : "article";
// Favicon precedence: svg > ico > png. The first file that exists wins;
// when none exists we still emit the svg link so users who drop one in
// later don't need to touch the layout.
const faviconHref = `${import.meta.env.BASE_URL}${headDefaults.favicon.file}`;
const faviconHref = withBase(headDefaults.favicon.file, baseUrl);
const faviconType = headDefaults.favicon.type;

// Social image precedence: page prop > config > `public/opengraph.png` >
Expand Down Expand Up @@ -205,7 +205,7 @@ const structuredData = canonical
// Per-HTML last-element-wins semantics: page extras come after config
// extras so per-page overrides win for any duplicated single-value tag.
const mergedHead = [...(config.head ?? []), ...pageHead];
const shikiCssHref = `${import.meta.env.BASE_URL}_nimbus/shiki.css${import.meta.env.DEV ? `?v=${Date.now()}` : ""}`;
const shikiCssHref = `${withBase("/_nimbus/shiki.css", baseUrl)}${import.meta.env.DEV ? `?v=${Date.now()}` : ""}`;
---

<title>{fullTitle}</title>
Expand Down
69 changes: 69 additions & 0 deletions packages/nimbus-docs/test/subpath-template-contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
import path from "node:path";

const repoRoot = path.resolve(
fileURLToPath(new URL("../../..", import.meta.url)),
);

function source(relativePath: string): string {
return readFileSync(path.join(repoRoot, relativePath), "utf8");
}

test("framework and starter-owned navigation apply Astro's base path", () => {
const head = source("packages/nimbus-docs/src/components/NimbusHead.astro");
assert.match(
head,
/const faviconHref = withBase\(headDefaults\.favicon\.file, baseUrl\);/,
);
assert.match(head, /withBase\("\/_nimbus\/shiki\.css", baseUrl\)/);

const starterRoot = "packages/nimbus-starter-source/src";
const header = source(`${starterRoot}/components/Header.astro`);
assert.match(
header,
/const homeHref = withBase\("\/", import\.meta\.env\.BASE_URL\);/,
);
assert.match(header, /href=\{homeHref\}/);
assert.match(
header,
/href=\{withBase\(section\.href, import\.meta\.env\.BASE_URL\)\}/,
);

const sidebarLink = source(
`${starterRoot}/components/ui/sidebar/SidebarLink.astro`,
);
assert.match(
sidebarLink,
/const hrefWithBase = withBase\(href, import\.meta\.env\.BASE_URL\);/,
);
assert.match(sidebarLink, /href=\{hrefWithBase\}/);

const breadcrumbs = source(
`${starterRoot}/components/ui/breadcrumbs/Breadcrumbs.astro`,
);
assert.match(breadcrumbs, /href=\{hrefWithBase\(crumb\.href\)\}/);

const pagination = source(
`${starterRoot}/components/ui/pagination/Pagination.astro`,
);
assert.match(
pagination,
/const prevHref = prev && withBase\(prev\.href, import\.meta\.env\.BASE_URL\);/,
);
assert.match(
pagination,
/const nextHref = next && withBase\(next\.href, import\.meta\.env\.BASE_URL\);/,
);

assert.match(
source(`${starterRoot}/layouts/DocsLayout.astro`),
/const homeHref = withBase\("\/", import\.meta\.env\.BASE_URL\);/,
);
assert.match(
source(`${starterRoot}/pages/404.astro`),
/const homeHref = withBase\("\/", import\.meta\.env\.BASE_URL\);/,
);
});
7 changes: 4 additions & 3 deletions packages/nimbus-starter-source/src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LinkButton } from "./ui/link-button";
import { ThemeToggle } from "./ui/theme-toggle";
import SearchTrigger from "./ui/search/SearchTrigger.astro";
import { config } from "virtual:nimbus/config";
import { getSidebarSections } from "@cloudflare/nimbus-docs/runtime";
import { getSidebarSections, withBase } from "@cloudflare/nimbus-docs/runtime";

interface Props {
/** Astro collection id for the current page, forwarded from DocsLayout. */
Expand All @@ -32,13 +32,14 @@ const currentSlug = Astro.url.pathname.replace(/\/$/, "") || "/";
const sections =
sectionsProp ?? (await getSidebarSections(currentSlug, { collection }));
const showSections = sections.length >= 2;
const homeHref = withBase("/", import.meta.env.BASE_URL);
---

<header class="sticky top-0 z-30 border-b border-border bg-card/80 backdrop-blur-lg">
<div class="mx-auto flex items-center justify-between gap-3 h-14 px-4 lg:px-6">
<div class="flex items-center gap-3 min-w-0">
<slot name="brand">
<a href="/" class="group shrink-0">
<a href={homeHref} class="group shrink-0">
<span class="font-semibold text-sm text-foreground group-hover:text-primary transition-colors">
{config.title}
</span>
Expand All @@ -49,7 +50,7 @@ const showSections = sections.length >= 2;
<nav aria-label="Sections" class="hidden md:flex items-center gap-1 ml-4 pl-4 border-l border-border h-6 min-w-0 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
{sections.map((section) => (
<a
href={section.href}
href={withBase(section.href, import.meta.env.BASE_URL)}
aria-current={section.isActive ? "page" : undefined}
class:list={[
"px-2 py-1 rounded-md text-[0.8125rem] font-medium no-underline transition-colors shrink-0 whitespace-nowrap",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { cn } from "@/lib/cn";
import { withBase } from "@cloudflare/nimbus-docs/runtime";
import type { HTMLAttributes } from "astro/types";
import type { Breadcrumb } from "@cloudflare/nimbus-docs/types";

Expand All @@ -26,6 +27,8 @@ const tailCount = 2; // parent + current page
const headItems = shouldCollapse ? items.slice(0, headCount) : items;
const collapsedItems = shouldCollapse ? items.slice(headCount, items.length - tailCount) : [];
const tailItems = shouldCollapse ? items.slice(items.length - tailCount) : [];
const hrefWithBase = (href: string): string =>
withBase(href, import.meta.env.BASE_URL);
---

{items.length > (endsAtCurrentPage ? 1 : 0) && (
Expand All @@ -37,7 +40,7 @@ const tailItems = shouldCollapse ? items.slice(items.length - tailCount) : [];
{isCurrent(!shouldCollapse && i === items.length - 1) || !crumb.href ? (
<span class="text-foreground truncate max-w-[12rem]">{crumb.label}</span>
) : (
<a href={crumb.href} class="hover:text-foreground transition-colors truncate max-w-[12rem]">
<a href={hrefWithBase(crumb.href)} class="hover:text-foreground transition-colors truncate max-w-[12rem]">
{crumb.label}
</a>
)}
Expand All @@ -58,7 +61,7 @@ const tailItems = shouldCollapse ? items.slice(items.length - tailCount) : [];
{collapsedItems.map((crumb) => (
crumb.href ? (
<a
href={crumb.href}
href={hrefWithBase(crumb.href)}
class="block truncate rounded-md px-2.5 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
>
{crumb.label}
Expand All @@ -80,7 +83,7 @@ const tailItems = shouldCollapse ? items.slice(items.length - tailCount) : [];
{isCurrent(i === tailItems.length - 1) || !crumb.href ? (
<span class="text-foreground truncate max-w-[12rem]">{crumb.label}</span>
) : (
<a href={crumb.href} class="hover:text-foreground transition-colors truncate max-w-[12rem]">
<a href={hrefWithBase(crumb.href)} class="hover:text-foreground transition-colors truncate max-w-[12rem]">
{crumb.label}
</a>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import Icon from "@cloudflare/nimbus-docs/components/Icon.astro";
import { withBase } from "@cloudflare/nimbus-docs/runtime";
import { cn } from "@/lib/cn";
import type { HTMLAttributes } from "astro/types";
import type { PrevNext } from "@cloudflare/nimbus-docs/types";
Expand All @@ -10,12 +11,14 @@ interface Props extends HTMLAttributes<"nav"> {

const { prevNext, class: className, ...attrs } = Astro.props;
const { prev, next } = prevNext;
const prevHref = prev && withBase(prev.href, import.meta.env.BASE_URL);
const nextHref = next && withBase(next.href, import.meta.env.BASE_URL);
---

{(prev || next) && (
<nav aria-label="Pagination" class={cn("flex items-center justify-between mt-12 pt-6 border-t border-border", className)} {...attrs}>
{prev ? (
<a href={prev.href} class="group flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors">
<a href={prevHref} class="group flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors">
<Icon name="ph:caret-left" class="w-4 h-4 transition-transform group-hover:-translate-x-0.5" />
<span>
<span class="block text-xs text-muted-foreground">Previous</span>
Expand All @@ -24,7 +27,7 @@ const { prev, next } = prevNext;
</a>
) : <span />}
{next ? (
<a href={next.href} class="group flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors text-right">
<a href={nextHref} class="group flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors text-right">
<span>
<span class="block text-xs text-muted-foreground">Next</span>
<span class="font-medium text-foreground">{next.label}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { cn } from "@/lib/cn";
import type { HTMLAttributes } from "astro/types";
import { Badge } from "@/components/ui/badge";
import { withBase } from "@cloudflare/nimbus-docs/runtime";
import type { SidebarBadge } from "@cloudflare/nimbus-docs/types";

interface Props extends HTMLAttributes<"a"> {
Expand All @@ -16,10 +17,11 @@ interface Props extends HTMLAttributes<"a"> {
}

const { label, href, isCurrent, badge, class: className, ...attrs } = Astro.props;
const hrefWithBase = withBase(href, import.meta.env.BASE_URL);
---

<a
href={href}
href={hrefWithBase}
aria-current={isCurrent ? "page" : undefined}
data-nb-sidebar-link
class={cn(
Expand Down
16 changes: 12 additions & 4 deletions packages/nimbus-starter-source/src/layouts/DocsLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { Pagination } from "@/components/ui/pagination";
import { PageActions } from "@/components/ui/page-actions";
import { Badge } from "@/components/ui/badge";
import type { DocsPageProps } from "@cloudflare/nimbus-docs/types";
import { getVersionStatus, getVersionAlternates } from "@cloudflare/nimbus-docs/runtime";
import {
getVersionStatus,
getVersionAlternates,
withBase,
} from "@cloudflare/nimbus-docs/runtime";

type Props = DocsPageProps & { audience?: "human" };

Expand All @@ -40,6 +44,10 @@ const versionAlternates = collection && entryId
? await getVersionAlternates(collection, entryId)
: null;
const currentSiblingUrl = versionAlternates?.canonical?.url ?? null;
const currentSiblingHref = currentSiblingUrl
? withBase(currentSiblingUrl, import.meta.env.BASE_URL)
: null;
const homeHref = withBase("/", import.meta.env.BASE_URL);

// Page-body attribute resolution:
// - If the user opted out via `searchable: false` (or `noindex: true`
Expand Down Expand Up @@ -187,9 +195,9 @@ const hasPagination = Astro.slots.has("pagination");
<Banner
variant="caution"
content={
currentSiblingUrl
? `This is the <strong>${versionStatus.version}</strong> version of the docs and is no longer maintained. The latest version of this page is at <a href="${currentSiblingUrl}">${currentSiblingUrl}</a>.`
: `This is the <strong>${versionStatus.version}</strong> version of the docs and is no longer maintained. See the <a href="/">current docs</a> for up-to-date content.`
currentSiblingHref
? `This is the <strong>${versionStatus.version}</strong> version of the docs and is no longer maintained. The latest version of this page is at <a href="${currentSiblingHref}">${currentSiblingHref}</a>.`
: `This is the <strong>${versionStatus.version}</strong> version of the docs and is no longer maintained. See the <a href="${homeHref}">current docs</a> for up-to-date content.`
}
/>
)}
Expand Down
4 changes: 3 additions & 1 deletion packages/nimbus-starter-source/src/pages/404.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import BaseLayout from "../layouts/BaseLayout.astro";
import Header from "../components/Header.astro";
import { config } from "virtual:nimbus/config";
import { withBase } from "@cloudflare/nimbus-docs/runtime";

// Prerender the 404 so it stays a static route under `output: "server"`.
// Hosts serve it via their not-found handling (e.g. Cloudflare
// `not_found_handling: "404-page"`); the adapter adds no on-demand route.
export const prerender = true;
const homeHref = withBase("/", import.meta.env.BASE_URL);
---

<BaseLayout title={`Page not found · ${config.title}`} description="The page you're looking for doesn't exist.">
Expand All @@ -24,7 +26,7 @@ export const prerender = true;
The page you're looking for doesn't exist or has moved.
</p>
<a
href="/"
href={homeHref}
class="mt-8 inline-block rounded-lg border border-border px-4 py-2 font-medium text-foreground no-underline transition-colors hover:border-border-strong"
>
Back home
Expand Down