Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
146 changes: 0 additions & 146 deletions .cursor/rules/librechat-documentation.mdc

This file was deleted.

3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ jobs:
- name: Check formatting
run: pnpm lint:prettier

- name: Check config versions
run: pnpm check:config-version

- name: Typecheck
run: pnpm typecheck

Expand Down
16 changes: 9 additions & 7 deletions .github/workflows/links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,21 @@ jobs:

# Root-relative links (/docs, /images, ...) are app routes/assets that do
# not map to local files (content lives under content/ and routes are
# extensionless). --root-dir lets lychee resolve them to
# $GITHUB_WORKSPACE/<seg> instead of erroring with "provide a root dir";
# we then exclude them, since they can't be checked on disk. The exclude
# is anchored at the regex-escaped workspace so it does NOT also skip
# file-relative links (./LICENSE, ../../features/foo.md), which
# legitimately resolve under $GITHUB_WORKSPACE/content/ and stay checked.
# extensionless). Archived docs routes (/vX.Y[.Z|.x][-rcN]/docs/...) likewise
# have no checkout path at the repository root. --root-dir lets lychee
Comment on lines +37 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude frozen snapshots from link-check inputs

In the inspected .github/workflows/links.yml, the new route regex excludes archived URL targets but the collection step at lines 63-64 still scans every English file under content, including content/docs-archive. With the committed snapshots this expands the check from 189 live English docs to 2,389 archived MDX files, repeatedly checking duplicated external links and opening issues for link rot in snapshots that are explicitly frozen and should not be edited. Exclude content/docs-archive from the input file list while retaining the archived-route exclusion for links encountered elsewhere.

Useful? React with πŸ‘Β / πŸ‘Ž.

# resolve them to $GITHUB_WORKSPACE/<seg> instead of erroring with
# "provide a root dir"; we then exclude them, since they can't be checked
# on disk. The exclude is anchored at the regex-escaped workspace so it
# does NOT also skip file-relative links (./LICENSE, ../../features/foo.md),
# which legitimately resolve under $GITHUB_WORKSPACE/content/ and stay
# checked.
- name: Build internal-route exclude pattern
id: routes
env:
WS: ${{ github.workspace }}
run: |
esc=$(printf '%s' "$WS" | sed -E 's/[][(){}.^$*+?|\\]/\\&/g')
echo "pattern=^file://${esc}/(docs|blog|changelog|images|videos|assets|toolkit)(/|\$)" >> "$GITHUB_OUTPUT"
echo "pattern=^file://${esc}/(docs|blog|changelog|images|videos|assets|toolkit|v[0-9]+\.[0-9]+(\.[0-9]+|\.x)?(-rc[0-9]*)?/docs)(/|\$)" >> "$GITHUB_OUTPUT"
# scripts/translate.ts generates content/docs/**/*.<xx>.mdx from the English
# source. Each translated copy's in-page anchors still point at the English
Expand Down
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ public/starters-hashes.json
# and the translation memory. English meta.json is still formatted.
content/docs/**/meta.*.json
content/.i18n-cache/

# Frozen docs snapshots: byte-for-byte copies of a released version's content
# (see content/docs-archive/README.md). Reformatting them would rewrite history.
content/docs-archive/
75 changes: 60 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,23 +171,68 @@ Only pages listed in the `pages` array appear in the sidebar, in the order given

**Localization:** English (`.mdx`) is the source of truth. Translated pages use a locale suffix (for example `index.es.mdx`), and each locale's search index only includes pages that have a real translated file. Keep new content in English and let the translation workflow handle the rest.

## Docs Versions

`content/docs/` is the live version, served at `/docs` and labelled by `CURRENT_VERSION` in
`lib/versions.ts`. Every published release and release candidate is a frozen snapshot under
`content/docs-archive/<version>/`, served at `/<version>/docs` β€” for example
`/v0.8.5/docs/quick_start`. Version ids match `v<major>.<minor>[.<patch>|.x][-rc<n>]`; the
switcher orders them newest-first, with a release ranked above its own candidates.

Publish one with:

```bash
pnpm docs:archive v0.8.5 --ref=<commit-or-tag>
```

That copies the English docs out of git at `--ref` (default `HEAD`), drops every localized file,
and rewrites absolute `/docs/...` links to `/<version>/docs/...` so an archived page never links
back into the live docs. The sidebar version switcher is built from the directories that exist, so
no code change is needed to publish or retire a version.

Archived snapshots are deliberately inert: English-only, `noindex`, absent from the sitemap,
search index, `llms.txt`, the translation workflow and `pnpm sync:config-version` (they keep the
`librechat.yaml` version they shipped with).

Two limits are worth knowing:

- **The archive starts at v0.8.2.** This repository only gained `content/docs` in the Fumadocs
migration, and every release up to v0.8.2 had its changelog backfilled in that single commit, so
there is exactly one docs tree for all of them β€” published once as `v0.8.2`. Docs for v0.5.x–v0.8.1
exist only as the pre-migration Nextra `pages/` tree and would need converting before they could
be archived.
- **Snapshots are bundled, so they cost build memory.** Archived pages inherit real MDX imports
(`next/image`, `@/components/...`) from the docs they snapshot, which on-demand compilation
cannot resolve, so the collection is bundled like the live docs. `pnpm build` therefore runs with
`--max-old-space-size=8192`. The bundle also has a hard ceiling: with 17 archived versions webpack
fails with `RangeError: Invalid string length`, so only final releases are archived and a
release's candidate snapshots are deleted once it ships (see `content/docs-archive/README.md`).

## Available Scripts

| Command | Description |
| -------------------------- | --------------------------------------------- |
| `pnpm dev` | Start the dev server on port 3333 |
| `pnpm build` | Production build |
| `pnpm start` | Start the production server on port 3333 |
| `pnpm lint` | Run ESLint (zero warnings allowed) |
| `pnpm lint:prettier` | Check formatting with Prettier |
| `pnpm prettier` | Format the codebase with Prettier |
| `pnpm typecheck` | Generate MDX types and run `tsc --noEmit` |
| `pnpm test` | Run the Vitest suite |
| `pnpm test:watch` | Run Vitest in watch mode |
| `pnpm analyze` | Build and analyze the production bundle size |
| `pnpm optimize:images` | Optimize images in `public/` |
| `pnpm web-bot-auth:keygen` | Generate an Ed25519 Web Bot Auth private JWK |
| `pnpm translate` | Generate translations from the English source |
| Command | Description |
| --------------------------- | ------------------------------------------------------------ |
| `pnpm dev` | Start the dev server on port 3333 |
| `pnpm build` | Production build |
| `pnpm start` | Start the production server on port 3333 |
| `pnpm lint` | Run ESLint (zero warnings allowed) |
| `pnpm lint:prettier` | Check formatting with Prettier |
| `pnpm prettier` | Format the codebase with Prettier |
| `pnpm typecheck` | Generate MDX types and run `tsc --noEmit` |
| `pnpm test` | Run the Vitest suite |
| `pnpm test:watch` | Run Vitest in watch mode |
| `pnpm check:config-version` | Fail if any `librechat.yaml` snippet has a stale `version:` |
| `pnpm sync:config-version` | Rewrite stale `librechat.yaml` `version:` snippets to latest |
| `pnpm analyze` | Build and analyze the production bundle size |
| `pnpm optimize:images` | Optimize images in `public/` |
| `pnpm web-bot-auth:keygen` | Generate an Ed25519 Web Bot Auth private JWK |
| `pnpm translate` | Generate translations from the English source |
| `pnpm docs:archive` | Snapshot the English docs into `content/docs-archive` |

The config version is sourced from the newest `content/changelog/config_v*.mdx` entry, whose
`version:` frontmatter must match its filename. Never hand-edit the `version:` line in a docs
snippet β€” run `pnpm sync:config-version`, which updates every `librechat.yaml` code fence under
`content/docs` (all locales included) and leaves unrelated YAML alone.

## Contributing

Expand Down
24 changes: 20 additions & 4 deletions app/[lang]/docs/[[...slug]]/page.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
import { generateDocsMetadata, generateLocalizedDocsParams, renderDocsPage } from '@/lib/docs-page'
import {
generateArchivedDocsMetadata,
generateArchivedDocsParams,
generateDocsMetadata,
generateLocalizedDocsParams,
renderArchivedDocsPage,
renderDocsPage,
} from '@/lib/docs-page'
import { isArchivedVersion } from '@/lib/docs-archive'

interface PageProps {
params: Promise<{ lang: string; slug?: string[] }>
}

export default async function Page({ params }: PageProps) {
return renderDocsPage(await params)
const { lang, slug } = await params

if (isArchivedVersion(lang)) return renderArchivedDocsPage({ version: lang, slug })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add archived routes to the CDN cache rules

Once an archive is published, requests handled by this branch use /<version>/docs/..., but the inspected cdnCacheHeaders in next.config.mjs only match /docs/:path* and the enumerated locale-prefixed routes. The configuration's own comment notes that docs paths without a matching rule are never edge-cached by Cloudflare, so every archived-page request and navigation incurs an origin round trip while equivalent live documentation is cached. Include the archived URL shape in the same document/RSC cache rules.

Useful? React with πŸ‘Β / πŸ‘Ž.


return renderDocsPage({ lang, slug })
}

export function generateStaticParams() {
return generateLocalizedDocsParams()
return [...generateLocalizedDocsParams(), ...generateArchivedDocsParams()]
}

export async function generateMetadata({ params }: PageProps) {
return generateDocsMetadata(await params)
const { lang, slug } = await params

if (isArchivedVersion(lang)) return generateArchivedDocsMetadata({ version: lang, slug })

return generateDocsMetadata({ lang, slug })
}
12 changes: 12 additions & 0 deletions app/[lang]/docs/layout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { isArchivedVersion } from '@/lib/docs-archive'
import { renderDocsLayout } from '@/lib/docs-layout'
import { i18n } from '@/lib/i18n'
import type { ReactNode } from 'react'

/**
* The `[lang]` segment carries either a locale (`/es/docs/...`) or an archived
* docs version (`/v0.7.x/docs/...`) β€” Next.js can't give `/docs/[[...slug]]` a
* dynamic sibling segment, so the version shares the locale slot.
*/
export default async function Layout({
params,
children,
Expand All @@ -9,5 +16,10 @@ export default async function Layout({
children: ReactNode
}) {
const { lang } = await params

if (isArchivedVersion(lang)) {
return renderDocsLayout({ lang: i18n.defaultLanguage, children, archivedVersion: lang })
}

return renderDocsLayout({ lang, children })
}
46 changes: 46 additions & 0 deletions components/ArchivedVersionBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Callout } from 'fumadocs-ui/components/callout'

/**
* Shown at the top of every archived docs page. `currentHref` is the same slug
* on the live docs when it still exists, otherwise the live docs root. The
* newest release is still what most deployments run, so it is introduced as
* the latest release rather than as frozen, unmaintained docs.
*/
export function ArchivedVersionBanner({
version,
currentHref,
latestRelease = false,
}: {
version: string
currentHref: string
latestRelease?: boolean
}) {
if (latestRelease) {
return (
<Callout type="info" title="Latest release">
<p className="m-0">
You are reading the documentation for <strong>{version}</strong>, the latest release. The
main docs follow the development branch and can describe features that are not released
yet.{' '}
<a href={currentHref} className="font-medium underline">
Go to the latest docs
</a>
.
</p>
</Callout>
)
}

return (
<Callout type="warn" title="Archived documentation">
<p className="m-0">
You are reading the frozen documentation for <strong>{version}</strong>, which is no longer
maintained.{' '}
<a href={currentHref} className="font-medium underline">
Go to the latest docs
</a>
.
</p>
</Callout>
)
}
Loading
Loading