From ea2a7a4ac72a463e484663789afa77bc6bf4ddfc Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Wed, 16 Sep 2026 18:12:35 -0600 Subject: [PATCH 1/6] fix: prefix basePath on root-relative src Production serves under /docs. next/link adds the prefix, and next/image do not, so /sourcegraph-mark.svg in the mobile nav and the LinkCards/ProductCards icons, plus a couple of root-relative markdown images, 404 on sourcegraph.com. --- src/components/MobileNavigation.tsx | 3 ++- src/components/mdx/LinkCards.tsx | 7 ++++++- src/components/mdx/ProductCards.tsx | 7 ++++++- src/components/mdx/ZoomableImage.tsx | 12 +++++++++++- src/lib/utils.ts | 9 +++++++++ 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/components/MobileNavigation.tsx b/src/components/MobileNavigation.tsx index a59ac44db..aff20d9f1 100644 --- a/src/components/MobileNavigation.tsx +++ b/src/components/MobileNavigation.tsx @@ -6,6 +6,7 @@ import Link from 'next/link'; import {usePathname, useSearchParams} from 'next/navigation'; import {Dialog} from '@headlessui/react'; import {Navigation} from '@/components/Navigation'; +import {withBasePath} from '@/lib/utils'; function MenuIcon(props: React.ComponentPropsWithoutRef<'svg'>) { return ( @@ -100,7 +101,7 @@ export function MobileNavigation() { Sourcegraph Docs
{/* eslint-disable-next-line @next/next/no-img-element -- Small MDX icons do not need image optimization. */} - {imgAlt} + {imgAlt}

{title} diff --git a/src/components/mdx/ProductCards.tsx b/src/components/mdx/ProductCards.tsx index 38f0f7d03..9e58f5646 100644 --- a/src/components/mdx/ProductCards.tsx +++ b/src/components/mdx/ProductCards.tsx @@ -1,5 +1,6 @@ import Link from 'next/link'; import {CustomLink} from './CustomLink'; +import {withBasePath} from '@/lib/utils'; export function ProductCards({children}: {children: React.ReactNode}) { return ( @@ -29,7 +30,11 @@ export function ProductCard({
{/* eslint-disable-next-line @next/next/no-img-element -- Small MDX icons do not need image optimization. */} - {imgAlt} + {imgAlt}

diff --git a/src/components/mdx/ZoomableImage.tsx b/src/components/mdx/ZoomableImage.tsx index ef395215c..09a87389a 100644 --- a/src/components/mdx/ZoomableImage.tsx +++ b/src/components/mdx/ZoomableImage.tsx @@ -1,10 +1,20 @@ 'use client'; import {useCallback, useEffect, useState} from 'react'; +import {withBasePath} from '@/lib/utils'; interface ZoomableImageProps extends React.ImgHTMLAttributes {} -export function ZoomableImage({className, alt, ...props}: ZoomableImageProps) { +export function ZoomableImage({ + className, + alt, + src, + ...rest +}: ZoomableImageProps) { + const props = { + ...rest, + src: typeof src === 'string' ? withBasePath(src) : src + }; const [isOpen, setIsOpen] = useState(false); const openModal = useCallback(() => setIsOpen(true), []); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 065c19661..4018927aa 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,3 +4,12 @@ import {twMerge} from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +// Production serves the site under /docs (basePath in next.config.js). next/link +// adds it to hrefs, but and next/image do not, so a root-relative path +// to a file in public/ 404s on sourcegraph.com unless prefixed here. +export function withBasePath(url: string) { + const basePath = process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''; + const isRootRelative = url.startsWith('/') && !url.startsWith('//'); + return isRootRelative ? `${basePath}${url}` : url; +} From 4c38eb7090fe15119fc182a62b950d38b3800f01 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Thu, 17 Sep 2026 02:15:24 -0600 Subject: [PATCH 2/6] cspell: allow hrefs Amp-Thread-ID: https://ampcode.com/threads/T-01a0ae6a-1645-702a-a5ed-92f8b9bce33b Co-authored-by: Amp --- cspell-allow-list.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell-allow-list.txt b/cspell-allow-list.txt index c8d8eac88..d39625efe 100644 --- a/cspell-allow-list.txt +++ b/cspell-allow-list.txt @@ -240,6 +240,7 @@ horsegraph horsten hostmatcher hostpath +hrefs HSTS httptest huggingface From b5c5ac187636030e3e86daddff4a02173146fe5c Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Thu, 17 Sep 2026 02:45:42 -0600 Subject: [PATCH 3/6] site: serve every deployment under /docs Previews and local dev served at / while production served at /docs, so a root-relative only 404d after deploy. Use basePath /docs everywhere; the / -> /docs redirect from #1955 now applies to every deployment. Update the tooling that assumed previews had no basePath. Amp-Thread-ID: https://ampcode.com/threads/T-01a0ae6a-1645-702a-a5ed-92f8b9bce33b Co-authored-by: Amp --- .amp/services.yaml | 4 ++-- .github/workflows/preview-links.yml | 4 ++-- AGENTS.md | 12 +++++++++++- README.md | 4 ++-- dev/verify-links-live.mjs | 7 +++---- next.config.js | 18 +++++++----------- src/lib/utils.ts | 6 +++--- 7 files changed, 30 insertions(+), 25 deletions(-) diff --git a/.amp/services.yaml b/.amp/services.yaml index 87e947eb5..73d0ae63a 100644 --- a/.amp/services.yaml +++ b/.amp/services.yaml @@ -4,7 +4,7 @@ services: port: 31420 env: NODE_OPTIONS: --max-old-space-size=3072 - health: /agentic-batch-changes + health: /docs/agentic-batch-changes portal: - url: /agentic-batch-changes + url: /docs/agentic-batch-changes title: Sourcegraph Docs diff --git a/.github/workflows/preview-links.yml b/.github/workflows/preview-links.yml index 70c688e0d..c4447b8f9 100644 --- a/.github/workflows/preview-links.yml +++ b/.github/workflows/preview-links.yml @@ -60,11 +60,11 @@ jobs: }); // Mirror contentlayer's flattenedPath: docs/.mdx -> /, - // with a trailing /index dropped. Preview deployments have no basePath. + // with a trailing /index dropped, under the /docs basePath. const pages = files .filter(f => f.filename.startsWith('docs/') && f.filename.endsWith('.mdx') && f.status !== 'removed') .map(f => f.filename.slice('docs/'.length, -'.mdx'.length).replace(/^index$/, '').replace(/\/index$/, '')) - .map(path => ({path, url: `${previewUrl}/${path}`})); + .map(path => ({path, url: `${previewUrl}/docs/${path}`})); if (pages.length === 0) { core.info(`PR #${pr.number} changes no docs pages; not commenting.`); diff --git a/AGENTS.md b/AGENTS.md index 5718c4250..8b8590af1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ script header for what it checks). Not part of `pnpm run check`: main has hundreds of pre-existing findings, and CI only reports the ones a PR adds - **Prove changed links resolve on a deploy**: - `node dev/verify-links-live.mjs --site ` prints a + `node dev/verify-links-live.mjs --site /docs` prints a Markdown table for the PR description - **Spell check**: `.github/workflows/spellcheck.yml` runs `dev/check-spelling.mjs` on the lines a PR adds plus its title and @@ -83,3 +83,13 @@ next run. (From a manifest), install it, copy its Bot User OAuth Token into the secret, and `/invite @Vercel build log` to the channel. + + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/README.md b/README.md index 398d259ba..dd6064ea3 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Next, run the development server: pnpm run dev ``` -Finally, open [`http://localhost:3000`](http://localhost:3000) in your browser +Finally, open [`http://localhost:3000/docs`](http://localhost:3000/docs) in your browser to view the website. ## Writing and contributing to Sourcegraph Docs @@ -225,7 +225,7 @@ instructions visit As you make changes to the documentation, the development server will automatically update. Review your changes by navigating to -`http://localhost:3000` in your browser. +`http://localhost:3000/docs` in your browser. ### Previewing Vercel Deployments diff --git a/dev/verify-links-live.mjs b/dev/verify-links-live.mjs index 7f4434a3e..b39d32f4b 100644 --- a/dev/verify-links-live.mjs +++ b/dev/verify-links-live.mjs @@ -13,11 +13,10 @@ // Prints a Markdown table to paste into a PR. Old links point at --old-site so // reviewers can see the current breakage. // -// node dev/verify-links-live.mjs --site https://.vercel.app [--old-site https://sourcegraph.com/docs] [--base origin/main] +// node dev/verify-links-live.mjs --site https://.vercel.app/docs [--old-site https://sourcegraph.com/docs] [--base origin/main] // -// Production serves under https://sourcegraph.com/docs (basePath in -// next.config.js); Vercel previews serve at the root, so pass the full prefix -// in --site. +// Every deployment serves under /docs (basePath in next.config.js), so --site +// includes that prefix. import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; diff --git a/next.config.js b/next.config.js index 2603ca187..d6d3e3c7a 100644 --- a/next.config.js +++ b/next.config.js @@ -1,12 +1,10 @@ const {withContentlayer} = require('next-contentlayer2'); /** @type {import('next').NextConfig} */ -// in prod, we serve the docs from sourcegraph.com/docs, and this requires special config on the GFE side -// in preview/development, this is not necessary. -// -// VERCEL_ENV is a system env var set by Vercel -// https://vercel.com/docs/projects/environment-variables/system-environment-variables -const basePath = process.env.VERCEL_ENV === 'production' ? '/docs' : ''; +// sourcegraph.com proxies /docs/* to this site. Previews and local dev use the +// same basePath so that a root-relative URL which 404s in production also 404s +// there, instead of only breaking after deploy. +const basePath = '/docs'; const nextConfig = { reactStrictMode: true, @@ -18,12 +16,10 @@ const nextConfig = { env: { NEXT_PUBLIC_DOCS_BASE_PATH: basePath }, - // With basePath set, nothing serves `/`, so the *.vercel.app deployment URL - // that Vercel links from Slack / GitHub 404s. sourcegraph.com never proxies - // `/` to us, so this only affects visitors opening that raw URL. Stay on the - // same host so they see this exact deployment, not whatever is live. + // Nothing serves `/`, so the deployment URLs Vercel links from Slack / GitHub + // and http://localhost:3000 would 404. sourcegraph.com never proxies `/` to + // us. Stay on the same host so visitors see this exact deployment. async redirects() { - if (!basePath) return []; return [ { source: '/', diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 4018927aa..fb935217b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -5,9 +5,9 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -// Production serves the site under /docs (basePath in next.config.js). next/link -// adds it to hrefs, but and next/image do not, so a root-relative path -// to a file in public/ 404s on sourcegraph.com unless prefixed here. +// The site is served under /docs (basePath in next.config.js). next/link adds it +// to hrefs, but and next/image do not, so a root-relative path to a +// file in public/ 404s unless prefixed here. export function withBasePath(url: string) { const basePath = process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''; const isRootRelative = url.startsWith('/') && !url.startsWith('//'); From cb698340467ed0653e9b7b8e50c5d5d5690434b3 Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Thu, 17 Sep 2026 03:07:29 -0600 Subject: [PATCH 4/6] site: one DOCS_BASE_PATH constant; drop NEXT_PUBLIC_DOCS_BASE_PATH basePath is declared once, in docs.config.js. next.config.js reads it for routing; everything that builds a URL Next does not prefix (img src, a href, fetch, metadata, proxy redirects) goes through withBasePath or reads the constant, instead of nine copies of process.env.NEXT_PUBLIC_DOCS_BASE_PATH or a hardcoded '/docs'. proxy.ts: request.nextUrl.pathname already excludes the basePath, so the path.replace('/docs', '') was a no-op that would have mangled any real path containing '/docs'. Removed. Amp-Thread-ID: https://ampcode.com/threads/T-01a0ae6a-1645-702a-a5ed-92f8b9bce33b Co-authored-by: Amp --- docs.config.js | 5 ++++- next.config.js | 13 +++--------- src/app/[...slug]/page.tsx | 3 ++- src/app/layout.tsx | 5 +++-- src/app/page.tsx | 3 ++- src/components/Logo.tsx | 8 +++---- src/components/NotFoundLinks.tsx | 8 +++---- src/components/ReleasesTable.tsx | 4 ++-- src/components/Toc.tsx | 4 ++-- src/components/search/Search.tsx | 7 +++---- src/lib/utils.ts | 9 ++++---- src/proxy.ts | 36 ++++++++++++++++---------------- 12 files changed, 50 insertions(+), 55 deletions(-) diff --git a/docs.config.js b/docs.config.js index 5fec13fd4..eebd37e96 100644 --- a/docs.config.js +++ b/docs.config.js @@ -1,5 +1,8 @@ const config = { - DOCS_LATEST_VERSION: '7.7' + DOCS_LATEST_VERSION: '7.7', + // sourcegraph.com proxies /docs/* to this site. Previews and local dev use the + // same basePath so a root-relative URL that 404s in production 404s there too. + DOCS_BASE_PATH: '/docs' }; module.exports = config; diff --git a/next.config.js b/next.config.js index d6d3e3c7a..687ed952f 100644 --- a/next.config.js +++ b/next.config.js @@ -1,21 +1,14 @@ const {withContentlayer} = require('next-contentlayer2'); +const {DOCS_BASE_PATH} = require('./docs.config.js'); /** @type {import('next').NextConfig} */ -// sourcegraph.com proxies /docs/* to this site. Previews and local dev use the -// same basePath so that a root-relative URL which 404s in production also 404s -// there, instead of only breaking after deploy. -const basePath = '/docs'; - const nextConfig = { reactStrictMode: true, - basePath, + basePath: DOCS_BASE_PATH, // Orb portals proxy the dev server through a different hostname. allowedDevOrigins: process.env.PUBLIC_URL ? [new URL(process.env.PUBLIC_URL).hostname] : [], - env: { - NEXT_PUBLIC_DOCS_BASE_PATH: basePath - }, // Nothing serves `/`, so the deployment URLs Vercel links from Slack / GitHub // and http://localhost:3000 would 404. sourcegraph.com never proxies `/` to // us. Stay on the same host so visitors see this exact deployment. @@ -23,7 +16,7 @@ const nextConfig = { return [ { source: '/', - destination: basePath, + destination: DOCS_BASE_PATH, basePath: false, permanent: false } diff --git a/src/app/[...slug]/page.tsx b/src/app/[...slug]/page.tsx index 9b5f38d8a..14445a892 100644 --- a/src/app/[...slug]/page.tsx +++ b/src/app/[...slug]/page.tsx @@ -4,6 +4,7 @@ import {PreviewGuard} from '@/components/PreviewGuard'; import {PrevNextLinks} from '@/components/PrevNextLinks'; import {Prose} from '@/components/Prose'; import {TableOfContents} from '@/components/Toc'; +import {withBasePath} from '@/lib/utils'; import {allPosts} from 'contentlayer/generated'; import {getMDXComponent} from 'next-contentlayer2/hooks'; import {notFound} from 'next/navigation'; @@ -28,7 +29,7 @@ export const generateMetadata = async ({params}: Props) => { const post = allPosts.find(post => post._raw.flattenedPath === path); if (post && post.headings && post.headings.length > 0) { const title = post.headings[0].title; - const ogImageUrl = `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/${path}`; + const ogImageUrl = withBasePath(`/api/og/${path}`); return { title, openGraph: { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3408ec9c0..97a20de64 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import {Providers} from '@/app/providers'; import {Layout} from '@/components/Layout'; import {TECHNICAL_CHANGELOG_RSS_URL} from '@/data/constants'; +import {withBasePath} from '@/lib/utils'; import clsx from 'clsx'; import config from 'docs.config'; import {type Metadata} from 'next'; @@ -26,11 +27,11 @@ export const metadata: Metadata = { canonical: '/docs' }, openGraph: { - images: [{url: `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/index`, width: 1200, height: 630}] + images: [{url: withBasePath('/api/og/index'), width: 1200, height: 630}] }, twitter: { card: 'summary_large_image', - images: [{url: `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/index`, width: 1200, height: 630}] + images: [{url: withBasePath('/api/og/index'), width: 1200, height: 630}] } }; diff --git a/src/app/page.tsx b/src/app/page.tsx index ab6f7e380..67ce11967 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,12 @@ import MdxComponents from '@/components/MdxComponents'; import {Prose} from '@/components/Prose'; +import {withBasePath} from '@/lib/utils'; import {allPosts} from 'contentlayer/generated'; import {getMDXComponent} from 'next-contentlayer2/hooks'; import {notFound} from 'next/navigation'; export const generateMetadata = () => { - const ogImageUrl = `${process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''}/api/og/index`; + const ogImageUrl = withBasePath('/api/og/index'); return { openGraph: { images: [{url: ogImageUrl, width: 1200, height: 630}] diff --git a/src/components/Logo.tsx b/src/components/Logo.tsx index 2acb056e7..dc6119f74 100644 --- a/src/components/Logo.tsx +++ b/src/components/Logo.tsx @@ -1,18 +1,18 @@ -export function Logo(props: React.ComponentPropsWithoutRef<'svg'>) { - const basePath = process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''; +import {withBasePath} from '@/lib/utils'; +export function Logo(props: React.ComponentPropsWithoutRef<'svg'>) { return ( <> {/* eslint-disable-next-line @next/next/no-img-element -- SVG logos do not need image optimization. */} Sourcegraph Docs {/* eslint-disable-next-line @next/next/no-img-element -- SVG logos do not need image optimization. */} Sourcegraph Docs diff --git a/src/components/NotFoundLinks.tsx b/src/components/NotFoundLinks.tsx index 9d532cb7b..0d0230562 100644 --- a/src/components/NotFoundLinks.tsx +++ b/src/components/NotFoundLinks.tsx @@ -4,6 +4,7 @@ import {usePreviousPathname} from '@/components/PreviousPathname'; import Link from 'next/link'; import {usePathname} from 'next/navigation'; import {useEffect, useMemo, useState} from 'react'; +import config from 'docs.config'; const linkClassName = 'text-sm font-medium text-slate-900 hover:underline dark:text-white'; @@ -22,9 +23,6 @@ function nearestExistingAncestor( return null; } -// Production serves the docs under /docs (basePath in next.config.js). -const basePath = process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''; - interface PageLink { href: string; pathname: string; @@ -39,8 +37,8 @@ function docsReferrer(): PageLink | null { if (!document.referrer) return null; const referrer = new URL(document.referrer); if (referrer.origin !== window.location.origin) return null; - if (!referrer.pathname.startsWith(`${basePath}/`)) return null; - const pathname = referrer.pathname.slice(basePath.length); + if (!referrer.pathname.startsWith(`${config.DOCS_BASE_PATH}/`)) return null; + const pathname = referrer.pathname.slice(config.DOCS_BASE_PATH.length); // The home link already covers the root. if (pathname === '/') return null; return {href: pathname + referrer.search + referrer.hash, pathname}; diff --git a/src/components/ReleasesTable.tsx b/src/components/ReleasesTable.tsx index 383830a37..bb81bc5f3 100644 --- a/src/components/ReleasesTable.tsx +++ b/src/components/ReleasesTable.tsx @@ -2,6 +2,7 @@ import {useEffect, useState} from 'react'; import Link from 'next/link'; +import {withBasePath} from '@/lib/utils'; type Release = { id: number; @@ -31,8 +32,7 @@ export function SupportedReleasesTable() { const [error, setError] = useState(null); useEffect(() => { - const basePath = process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''; - fetch(`${basePath}/api/releases`) + fetch(withBasePath('/api/releases')) .then(res => { if (!res.ok) throw new Error('Failed to fetch releases'); return res.json(); diff --git a/src/components/Toc.tsx b/src/components/Toc.tsx index 9d037c197..c62cae983 100644 --- a/src/components/Toc.tsx +++ b/src/components/Toc.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import Link from 'next/link'; import {useParams} from 'next/navigation'; import {useCallback, useEffect, useState} from 'react'; +import {withBasePath} from '@/lib/utils'; import {ClipboardIcon} from './icons/ClipboardIcon'; import {PencilIcon} from './icons/PencilIcon'; import {DocumentIcon} from './icons/DocumentIcon'; @@ -29,7 +30,6 @@ export function TableOfContents({headings, rawMarkdown, editPath}: Props) { let [currentSection, setCurrentSection] = useState(headings[0]?.id); const [copied, setCopied] = useState(false); const params: ParamsType = useParams(); - const basePath = process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''; const handleCopyPage = useCallback(async () => { if (!rawMarkdown) return; @@ -168,7 +168,7 @@ export function TableOfContents({headings, rawMarkdown, editPath}: Props) {

url.startsWith(PROD_DOCS_URL_PREFIX) - ? basePath + url.slice(PROD_DOCS_URL_PREFIX.length) + ? withBasePath(url.slice(PROD_DOCS_URL_PREFIX.length)) : url; const transformItems = (items: DocSearchHit[]): DocSearchHit[] => diff --git a/src/lib/utils.ts b/src/lib/utils.ts index fb935217b..67b891dc9 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,15 +1,14 @@ import {type ClassValue, clsx} from 'clsx'; +import config from 'docs.config'; import {twMerge} from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -// The site is served under /docs (basePath in next.config.js). next/link adds it -// to hrefs, but and next/image do not, so a root-relative path to a -// file in public/ 404s unless prefixed here. +// next/link and routing add the basePath; , , fetch() and +// metadata URLs do not, so root-relative URLs for those go through here. export function withBasePath(url: string) { - const basePath = process.env.NEXT_PUBLIC_DOCS_BASE_PATH || ''; const isRootRelative = url.startsWith('/') && !url.startsWith('//'); - return isRootRelative ? `${basePath}${url}` : url; + return isRootRelative ? `${config.DOCS_BASE_PATH}${url}` : url; } diff --git a/src/proxy.ts b/src/proxy.ts index 3d4e2bc10..6c328d446 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -49,19 +49,19 @@ function createRedirectUrl( } // Handle relative paths - const basePath = '/docs'; + const base = `${request.nextUrl.origin}${docsConfig.DOCS_BASE_PATH}`; return destination.startsWith('/') - ? `${request.nextUrl.origin}${basePath}${destination}` - : `${request.nextUrl.origin}${basePath}/${destination}`; + ? `${base}${destination}` + : `${base}/${destination}`; } export function proxy(request: NextRequest) { + // nextUrl.pathname excludes basePath, so /docs/x arrives here as /x. const path = request.nextUrl.pathname; - const pathWithoutBase = path.replace('/docs', ''); // Handle .md suffix - return raw markdown - if (pathWithoutBase.endsWith('.md')) { - const docPath = pathWithoutBase.replace(/\.md$/, ''); + if (path.endsWith('.md')) { + const docPath = path.replace(/\.md$/, ''); const url = request.nextUrl.clone(); url.pathname = `/api/md${docPath}`; return NextResponse.rewrite(url); @@ -69,7 +69,7 @@ export function proxy(request: NextRequest) { // Handle base redirects from redirects.ts const redirect = updatedRedirectsData.find( - (r: any) => r.source === pathWithoutBase + (r: any) => r.source === path ); if (redirect) { return NextResponse.redirect( @@ -78,7 +78,7 @@ export function proxy(request: NextRequest) { } // Handle latest version without path - redirect to main docs - const latestVersionOnlyMatch = pathWithoutBase.match( + const latestVersionOnlyMatch = path.match( `^\/(?:v\/|@)${docsConfig.DOCS_LATEST_VERSION}\/?$` ); if (latestVersionOnlyMatch) { @@ -86,7 +86,7 @@ export function proxy(request: NextRequest) { } // Handle version without slug - both /v/X.Y and @X.Y formats (for non-latest versions) - const versionOnlyMatch = pathWithoutBase.match( + const versionOnlyMatch = path.match( /^\/(?:v\/|@)(\d+\.\d+)\/?$/ ); if ( @@ -99,47 +99,47 @@ export function proxy(request: NextRequest) { } // Handle version-specific redirects - if (pathWithoutBase.startsWith(`/v/${docsConfig.DOCS_LATEST_VERSION}/`)) { + if (path.startsWith(`/v/${docsConfig.DOCS_LATEST_VERSION}/`)) { return NextResponse.redirect( createRedirectUrl( request, `https://sourcegraph.com/docs/:slug*`, - pathWithoutBase + path ) ); } - if (pathWithoutBase.startsWith(`/@${docsConfig.DOCS_LATEST_VERSION}/`)) { + if (path.startsWith(`/@${docsConfig.DOCS_LATEST_VERSION}/`)) { return NextResponse.redirect( createRedirectUrl( request, `https://sourcegraph.com/docs/:slug*`, - pathWithoutBase + path ) ); } - const versionMatch = pathWithoutBase.match(/^\/v\/(\d+\.\d+)\/(.*)/); + const versionMatch = path.match(/^\/v\/(\d+\.\d+)\/(.*)/); if (versionMatch) { return NextResponse.redirect( createRedirectUrl( request, 'https://:version.sourcegraph.com/:slug*', - pathWithoutBase + path ) ); } - const atVersionMatch = pathWithoutBase.match(/^\/@(\d+\.\d+)\/(.*)/); + const atVersionMatch = path.match(/^\/@(\d+\.\d+)\/(.*)/); if (atVersionMatch) { return NextResponse.redirect( createRedirectUrl( request, 'https://:version.sourcegraph.com/:slug*', - pathWithoutBase + path ) ); } - if (pathWithoutBase === '/changelog.rss') + if (path === '/changelog.rss') return NextResponse.redirect(TECHNICAL_CHANGELOG_RSS_URL); return NextResponse.next(); From 4251953f24a02d910aad7decdc9884fce10f892a Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Thu, 17 Sep 2026 03:08:45 -0600 Subject: [PATCH 5/6] proxy: keep latest-version redirects on the current deployment /v//x and /@/x redirected to https://sourcegraph.com/docs/x even from a preview or local dev, so a reviewer testing those paths on a preview silently landed on production. The current deployment serves the latest version, so redirect to its own origin + DOCS_BASE_PATH instead. Also merges the identical /v/ and /@ branches. Amp-Thread-ID: https://ampcode.com/threads/T-01a0ae6a-1645-702a-a5ed-92f8b9bce33b Co-authored-by: Amp --- src/proxy.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/proxy.ts b/src/proxy.ts index 6c328d446..386ed4fa6 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -82,7 +82,9 @@ export function proxy(request: NextRequest) { `^\/(?:v\/|@)${docsConfig.DOCS_LATEST_VERSION}\/?$` ); if (latestVersionOnlyMatch) { - return NextResponse.redirect(`https://sourcegraph.com/docs`); + return NextResponse.redirect( + `${request.nextUrl.origin}${docsConfig.DOCS_BASE_PATH}` + ); } // Handle version without slug - both /v/X.Y and @X.Y formats (for non-latest versions) @@ -98,21 +100,15 @@ export function proxy(request: NextRequest) { ); } - // Handle version-specific redirects - if (path.startsWith(`/v/${docsConfig.DOCS_LATEST_VERSION}/`)) { - return NextResponse.redirect( - createRedirectUrl( - request, - `https://sourcegraph.com/docs/:slug*`, - path - ) - ); - } - if (path.startsWith(`/@${docsConfig.DOCS_LATEST_VERSION}/`)) { + // Latest version with a slug: this deployment serves it, so stay here. + if ( + path.startsWith(`/v/${docsConfig.DOCS_LATEST_VERSION}/`) || + path.startsWith(`/@${docsConfig.DOCS_LATEST_VERSION}/`) + ) { return NextResponse.redirect( createRedirectUrl( request, - `https://sourcegraph.com/docs/:slug*`, + `${request.nextUrl.origin}${docsConfig.DOCS_BASE_PATH}/:slug*`, path ) ); From 87b9d786476f221a798560f5b95f7073a95de70d Mon Sep 17 00:00:00 2001 From: Marc LeBlanc <7050295+marcleblanc2@users.noreply.github.com> Date: Thu, 17 Sep 2026 03:27:27 -0600 Subject: [PATCH 6/6] site: derive every /docs and production URL from docs.config.js The basePath has moved three times. Next time, change DOCS_BASE_PATH (and DOCS_PROD_ORIGIN if the host moves) in docs.config.js and nothing else: - versions.ts, api/versions, VersionSelector, sitemap, robots, Search and the layout canonical/metadataBase read DOCS_BASE_PATH / DOCS_PROD_URL instead of spelling out /docs or https://sourcegraph.com/docs - check-redirects, check-links (self-link regex) and verify-links-live import docs.config.js - preview-links.yml checks out the PR's docs.config.js to build page URLs - search.ts searchDocumentsPath is root-relative like every public/ path; it is only a flag for writing public/search.json .amp/services.yaml is YAML and keeps its literal path, with a note. Amp-Thread-ID: https://ampcode.com/threads/T-01a0ae6a-1645-702a-a5ed-92f8b9bce33b Co-authored-by: Amp --- .amp/services.yaml | 1 + .github/workflows/preview-links.yml | 11 +++++++++-- dev/check-links.mjs | 19 +++++++++++++------ dev/check-redirects.mjs | 29 ++++++++++++++++------------- dev/verify-links-live.mjs | 9 +++++---- docs.config.js | 13 ++++++++++--- src/app/api/versions/route.ts | 3 ++- src/app/layout.tsx | 4 ++-- src/app/robots.ts | 3 ++- src/app/sitemap.ts | 7 +++---- src/components/VersionSelector.tsx | 3 ++- src/components/search/Search.tsx | 13 ++++++------- src/data/search.ts | 4 +++- src/data/versions.ts | 2 +- 14 files changed, 75 insertions(+), 46 deletions(-) diff --git a/.amp/services.yaml b/.amp/services.yaml index 73d0ae63a..e6614b0a4 100644 --- a/.amp/services.yaml +++ b/.amp/services.yaml @@ -4,6 +4,7 @@ services: port: 31420 env: NODE_OPTIONS: --max-old-space-size=3072 + # YAML cannot read DOCS_BASE_PATH from docs.config.js; keep these in step. health: /docs/agentic-batch-changes portal: url: /docs/agentic-batch-changes diff --git a/.github/workflows/preview-links.yml b/.github/workflows/preview-links.yml index c4447b8f9..20854b43f 100644 --- a/.github/workflows/preview-links.yml +++ b/.github/workflows/preview-links.yml @@ -29,6 +29,12 @@ jobs: if: github.event.client_payload.environment != 'production' runs-on: ubuntu-latest steps: + # The PR's own docs.config.js, for the basePath the preview serves under. + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.client_payload.git.sha || inputs.sha }} + sparse-checkout: docs.config.js + sparse-checkout-cone-mode: false - uses: actions/github-script@v7 env: PREVIEW_URL: ${{ github.event.client_payload.url || inputs.url }} @@ -36,6 +42,7 @@ jobs: with: script: | const {PREVIEW_URL, COMMIT_SHA} = process.env; + const {DOCS_BASE_PATH} = require(`${process.env.GITHUB_WORKSPACE}/docs.config.js`); if (!PREVIEW_URL || !/^[0-9a-f]{40}$/i.test(COMMIT_SHA ?? '')) { core.setFailed(`Need a preview URL and a full commit SHA; got url=${JSON.stringify(PREVIEW_URL)} sha=${JSON.stringify(COMMIT_SHA)}. ` + `Vercel sends these as client_payload.url and client_payload.git.sha; workflow_dispatch takes them as the url and sha inputs.`); @@ -60,11 +67,11 @@ jobs: }); // Mirror contentlayer's flattenedPath: docs/.mdx -> /, - // with a trailing /index dropped, under the /docs basePath. + // with a trailing /index dropped, under the basePath. const pages = files .filter(f => f.filename.startsWith('docs/') && f.filename.endsWith('.mdx') && f.status !== 'removed') .map(f => f.filename.slice('docs/'.length, -'.mdx'.length).replace(/^index$/, '').replace(/\/index$/, '')) - .map(path => ({path, url: `${previewUrl}/docs/${path}`})); + .map(path => ({path, url: `${previewUrl}${DOCS_BASE_PATH}/${path}`})); if (pages.length === 0) { core.info(`PR #${pr.number} changes no docs pages; not commenting.`); diff --git a/dev/check-links.mjs b/dev/check-links.mjs index f9fd80857..74620146a 100644 --- a/dev/check-links.mjs +++ b/dev/check-links.mjs @@ -46,6 +46,7 @@ import fs from 'fs'; import path from 'path'; import GithubSlugger from 'github-slugger'; import { fileURLToPath, pathToFileURL } from 'url'; +import config from '../docs.config.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -279,11 +280,17 @@ function extractLinks(content, filePath) { } // Absolute links to this site, in every form the docs have used: http or https, -// scheme-relative, www., the legacy docs.sourcegraph.com host, or sourcegraph.com/docs. -// Links pinned to an old version (/@5.1/..., /v/5.1/...) are external: the -// middleware sends them to that version's own site (5.1.sourcegraph.com), whose -// pages are not in this repo, so only --check-external can validate them. -const SELF_LINK_REGEX = /^(?:https?:)?\/\/(?:www\.)?(?:docs\.sourcegraph\.com|sourcegraph\.com\/docs)(?=[/#?]|$)(?!\/@|\/v\/)/i; +// scheme-relative, www., the legacy docs.sourcegraph.com host, or the production +// URL from docs.config.js. Links pinned to an old version (/@5.1/..., /v/5.1/...) +// are external: the middleware sends them to that version's own site +// (5.1.sourcegraph.com), whose pages are not in this repo, so only +// --check-external can validate them. +const escapeRegExp = text => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +const PROD_HOST_AND_PATH = config.DOCS_PROD_URL.replace(/^https?:\/\//, ''); +const SELF_LINK_REGEX = new RegExp( + `^(?:https?:)?//(?:www\\.)?(?:docs\\.sourcegraph\\.com|${escapeRegExp(PROD_HOST_AND_PATH)})(?=[/#?]|$)(?!/@|/v/)`, + 'i' +); export function isSelfLink(url) { return SELF_LINK_REGEX.test(url); @@ -580,7 +587,7 @@ function markdownFindingList(findings) { const ABSOLUTE_LINKS_ADVICE = 'Write links on this site as relative paths (`/admin/config/site-config`), ' + - 'not `https://sourcegraph.com/docs/…`: absolute links leave the preview ' + + `not \`${config.DOCS_PROD_URL}/…\`: absolute links leave the preview ` + 'deployment and local dev server, and hide moved pages behind redirects.'; // Body for a pull request comment. With --diff, findings are split into diff --git a/dev/check-redirects.mjs b/dev/check-redirects.mjs index f13831db8..f9242a95e 100644 --- a/dev/check-redirects.mjs +++ b/dev/check-redirects.mjs @@ -10,8 +10,8 @@ * - the source has no #fragment: browsers never send fragments, so such an * entry can never match * - the source has no earlier entry: the middleware uses the first match only - * - neither path starts with /docs: the middleware strips that prefix from - * requests and adds it to destinations + * - neither path starts with the basePath (docs.config.js): Next strips it + * from requests and the middleware adds it to destinations * - the destination is a page, not another redirect * - the destination page exists under docs/ (or is a file under public/) * - when the destination has a #fragment, the heading exists on that page @@ -39,11 +39,13 @@ import path from 'path'; import vm from 'vm'; import {fileURLToPath} from 'url'; import {extractHeadings, listFiles, routeFor} from './check-links.mjs'; +import config from '../docs.config.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const args = process.argv.slice(2); const ROOT_DIR = path.resolve(flagValue('--root') ?? path.dirname(__dirname)); +const BASE_PATH = config.DOCS_BASE_PATH; const FORMAT = flagValue('--format') ?? 'text'; const BASELINE_FILE = flagValue('--baseline'); const LINK_BASE = flagValue('--link-base')?.replace(/\/$/, ''); @@ -72,12 +74,12 @@ const PROBLEM = { 'If the redirect destination has a #fragment, it takes precedence, otherwise if the customer ' + "clicked a link which has a #fragment, it'll be kept and tried on the destination page." }, - docsPrefix: { - heading: 'Source or destination starts with /docs', + basePathPrefix: { + heading: `Source or destination starts with ${BASE_PATH}`, fix: - 'Write paths without the /docs prefix. The site removes /docs from the requested URL before ' + - 'matching sources, and adds it back in front of the destination, so a /docs/... source never ' + - 'matches and a /docs/... destination lands on /docs/docs/....' + `Write paths without the ${BASE_PATH} prefix. The site removes ${BASE_PATH} from the requested URL before ` + + `matching sources, and adds it back in front of the destination, so a ${BASE_PATH}/... source never ` + + `matches and a ${BASE_PATH}/... destination lands on ${BASE_PATH}${BASE_PATH}/....` }, duplicateSource: { heading: @@ -195,7 +197,8 @@ function findBrokenRedirects(redirects, headingsByRoute) { const report = (redirect, problem, {detail, fix} = {}) => findings.push({...redirect, problem: problem.heading, detail, fix}); const withoutFragment = url => url.split('#')[0]; - const withoutDocsPrefix = url => url.replace(/^\/docs(?=\/)/, ''); + const withoutBasePath = url => + url.startsWith(`${BASE_PATH}/`) ? url.slice(BASE_PATH.length) : url; const isRedirect = pathname => firstBySource.has(pathname) && !headingsByRoute.has(pathname); @@ -217,13 +220,13 @@ function findBrokenRedirects(redirects, headingsByRoute) { report(redirect, PROBLEM.shadowsPage); } if ( - source.pathname.startsWith('/docs/') || - redirect.destination.startsWith('/docs/') + source.pathname.startsWith(`${BASE_PATH}/`) || + redirect.destination.startsWith(`${BASE_PATH}/`) ) { - report(redirect, PROBLEM.docsPrefix, { + report(redirect, PROBLEM.basePathPrefix, { fix: { - source: withoutDocsPrefix(redirect.source), - destination: withoutDocsPrefix(redirect.destination) + source: withoutBasePath(redirect.source), + destination: withoutBasePath(redirect.destination) } }); continue; diff --git a/dev/verify-links-live.mjs b/dev/verify-links-live.mjs index b39d32f4b..f2120d68e 100644 --- a/dev/verify-links-live.mjs +++ b/dev/verify-links-live.mjs @@ -15,20 +15,21 @@ // // node dev/verify-links-live.mjs --site https://.vercel.app/docs [--old-site https://sourcegraph.com/docs] [--base origin/main] // -// Every deployment serves under /docs (basePath in next.config.js), so --site -// includes that prefix. +// Every deployment serves under the basePath (docs.config.js), so --site +// includes that prefix. Both default to production. import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import { extractHeadings } from './check-links.mjs'; +import config from '../docs.config.js'; const args = process.argv.slice(2); const argValue = (flag, fallback) => { const index = args.indexOf(flag); return index === -1 ? fallback : args[index + 1]; }; -const SITE = argValue('--site', 'https://sourcegraph.com/docs').replace(/\/$/, ''); -const OLD_SITE = argValue('--old-site', 'https://sourcegraph.com/docs').replace(/\/$/, ''); +const SITE = argValue('--site', config.DOCS_PROD_URL).replace(/\/$/, ''); +const OLD_SITE = argValue('--old-site', config.DOCS_PROD_URL).replace(/\/$/, ''); const BASE_REF = argValue('--base', 'origin/main'); const LINK = /\]\(([^)\s]+)\)|href=["']([^"']+)["']/g; diff --git a/docs.config.js b/docs.config.js index eebd37e96..c7218c2ed 100644 --- a/docs.config.js +++ b/docs.config.js @@ -1,8 +1,15 @@ +// sourcegraph.com proxies DOCS_BASE_PATH/* to this site. Previews and local dev +// use the same basePath so a root-relative URL that 404s in production 404s +// there too. To move the site, change DOCS_BASE_PATH here and the load balancer +// rule in sourcegraph/infrastructure; nothing else spells the path out. +const DOCS_BASE_PATH = '/docs'; +const DOCS_PROD_ORIGIN = 'https://sourcegraph.com'; + const config = { DOCS_LATEST_VERSION: '7.7', - // sourcegraph.com proxies /docs/* to this site. Previews and local dev use the - // same basePath so a root-relative URL that 404s in production 404s there too. - DOCS_BASE_PATH: '/docs' + DOCS_BASE_PATH, + DOCS_PROD_ORIGIN, + DOCS_PROD_URL: `${DOCS_PROD_ORIGIN}${DOCS_BASE_PATH}` }; module.exports = config; diff --git a/src/app/api/versions/route.ts b/src/app/api/versions/route.ts index 686d5c24e..b87d9c031 100644 --- a/src/app/api/versions/route.ts +++ b/src/app/api/versions/route.ts @@ -1,4 +1,5 @@ import {versions} from '@/data/versions'; +import config from 'docs.config'; import {NextResponse} from 'next/server'; // Next 15+ no longer caches GET handlers by default; the version list is @@ -9,7 +10,7 @@ export function GET() { return NextResponse.json( versions.map((version, index) => index === 0 - ? {...version, url: 'https://sourcegraph.com/docs'} + ? {...version, url: config.DOCS_PROD_URL} : version ), { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 97a20de64..3a696a774 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -12,7 +12,7 @@ import {Suspense} from 'react'; import '@/styles/tailwind.css'; export const metadata: Metadata = { - metadataBase: new URL('https://sourcegraph.com'), + metadataBase: new URL(config.DOCS_PROD_ORIGIN), title: { template: '%s - Sourcegraph docs', default: 'Sourcegraph docs' @@ -24,7 +24,7 @@ export const metadata: Metadata = { 'docsearch:version': `v${config.DOCS_LATEST_VERSION}` }, alternates: { - canonical: '/docs' + canonical: config.DOCS_BASE_PATH }, openGraph: { images: [{url: withBasePath('/api/og/index'), width: 1200, height: 630}] diff --git a/src/app/robots.ts b/src/app/robots.ts index 35d3bdba1..ed87f84e1 100644 --- a/src/app/robots.ts +++ b/src/app/robots.ts @@ -1,4 +1,5 @@ import {MetadataRoute} from 'next'; +import config from 'docs.config'; export default async function robots(): Promise { return { @@ -6,6 +7,6 @@ export default async function robots(): Promise { userAgent: '*', allow: '/' }, - sitemap: 'https://sourcegraph.com/docs/sitemap.xml' + sitemap: `${config.DOCS_PROD_URL}/sitemap.xml` }; } diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 618ceb52f..968493c84 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,12 +1,11 @@ import {MetadataRoute} from 'next'; import {allPosts} from 'contentlayer/generated'; +import config from 'docs.config'; export default async function sitemap(): Promise { - const baseUrl = 'https://sourcegraph.com/docs'; - const links: MetadataRoute.Sitemap = [ { - url: baseUrl, + url: config.DOCS_PROD_URL, lastModified: new Date(), changeFrequency: 'weekly', priority: 1.0 @@ -18,7 +17,7 @@ export default async function sitemap(): Promise { .forEach(post => { const priority = post.seoPriority; links.push({ - url: `${baseUrl}${post.url}`, + url: `${config.DOCS_PROD_URL}${post.url}`, lastModified: post.date ? new Date(post.date) : new Date(), changeFrequency: 'weekly', ...(priority !== undefined && priority !== 0.5 && {priority}) diff --git a/src/components/VersionSelector.tsx b/src/components/VersionSelector.tsx index 90b34b93c..4fcfa8cbe 100644 --- a/src/components/VersionSelector.tsx +++ b/src/components/VersionSelector.tsx @@ -8,13 +8,14 @@ import { ChevronDownIcon } from '@heroicons/react/20/solid'; import clsx from 'clsx'; +import config from 'docs.config'; import Link from 'next/link'; import {usePathname} from 'next/navigation'; import {Fragment, useEffect, useState} from 'react'; const versionsUrl = process.env.NEXT_PUBLIC_DOCS_VERSIONS_URL ?? - 'https://sourcegraph.com/docs/api/versions'; + `${config.DOCS_PROD_URL}/api/versions`; function isVersion(value: unknown): value is VersionI { if (typeof value !== 'object' || value === null) return false; diff --git a/src/components/search/Search.tsx b/src/components/search/Search.tsx index 33bd23e92..a8b6256bf 100644 --- a/src/components/search/Search.tsx +++ b/src/components/search/Search.tsx @@ -1,5 +1,6 @@ import {useEffect, useState} from 'react'; import {withBasePath} from '@/lib/utils'; +import config from 'docs.config'; import {searchMetadata} from '../../data/search'; import {DocSearch} from './docsearch/DocSearch'; import type {DocSearchHit} from './docsearch/types'; @@ -7,14 +8,12 @@ import './docsearch/docsearch.css'; // import '@docsearch/css'; -// The Algolia crawler indexes production, so every hit URL is absolute -// (https://sourcegraph.com/docs/...). Rewrite them to root-relative paths so -// results stay on whichever deployment is being viewed. -const PROD_DOCS_URL_PREFIX = 'https://sourcegraph.com/docs'; - +// The Algolia crawler indexes production, so every hit URL is absolute. +// Rewrite them to root-relative paths so results stay on whichever deployment +// is being viewed. const toLocalUrl = (url: string): string => - url.startsWith(PROD_DOCS_URL_PREFIX) - ? withBasePath(url.slice(PROD_DOCS_URL_PREFIX.length)) + url.startsWith(config.DOCS_PROD_URL) + ? withBasePath(url.slice(config.DOCS_PROD_URL.length)) : url; const transformItems = (items: DocSearchHit[]): DocSearchHit[] => diff --git a/src/data/search.ts b/src/data/search.ts index 51dea30e4..67628b830 100644 --- a/src/data/search.ts +++ b/src/data/search.ts @@ -1,7 +1,9 @@ export const searchMetadata = { provider: 'kbar', kbarConfig: { - searchDocumentsPath: '/docs/search.json' // path to load documents to search + // Root-relative like every public/ path; contentlayer.config.ts only checks + // it is set before writing public/search.json. + searchDocumentsPath: '/search.json' }, // For Algolia // provider: 'algolia', diff --git a/src/data/versions.ts b/src/data/versions.ts index c8ae90f08..a8899131f 100644 --- a/src/data/versions.ts +++ b/src/data/versions.ts @@ -11,7 +11,7 @@ export const versions: VersionI[] = [ { name: `v${config.DOCS_LATEST_VERSION}`, label: 'latest', - url: '/docs' + url: config.DOCS_BASE_PATH }, { name: 'v7.6',