Skip to main content

Legal Pages & Third-Party Licenses

The app footer pages (About, Terms of Service, Privacy Policy) and the Third-Party Licenses attribution page are sourced from shared Markdown files so that both the app ([tenant].rat.gd) and the docs site (docs.rat.gd) serve the same content from a single source.


File layout

docs/user/legal/
├── about.md ← English canonical (app + docs site)
├── terms.md
├── privacy.md
└── licenses.md ← generated by scripts/license-doc.mts (do not edit by hand)

website/i18n/{locale}/docusaurus-plugin-content-docs/current/legal/
└── *.md ← translations (app + docs site); mirror filenames of docs/user/legal/

All files use unlisted: true in frontmatter — they are reachable by direct URL (app footer links, docs site footer links) but hidden from the sidebar.

licenses.md is English-only; no website/i18n/*/legal/licenses.md files exist. The licenses/page.tsx always passes "en" to loadDoc explicitly.


App side (Next.js)

The legal pages live in their own (legal) route group (src/app/[locale]/(legal)/), outside the (app) group. This means they:

  • Have no app shell (no drawer, no navbar, no footer layout from the app)
  • Make no MongoDB calls and require no authentication
  • Are statically pre-rendered at build time ( in the build output)

Routes and their source slugs:

URLPage fileloadDoc slug
/[locale]/about(legal)/about/page.tsxlegal/about
/[locale]/terms(legal)/terms/page.tsxlegal/terms
/[locale]/privacy(legal)/privacy/page.tsxlegal/privacy
/[locale]/licenses(legal)/licenses/page.tsxlegal/licenses (always "en")
/[locale]/about/third-party-licenses(legal)/about/third-party-licenses/page.tsxlegal/licenses (always "en")

Each page exports dynamicParams = false and generateStaticParams over all supported locales, which triggers SSG. Pages call setRequestLocale(locale) (next-intl v4) and export generateMetadata derived from frontmatter.

loadDoc(slug, locale)src/lib/markdown/loadDoc.ts

Loads a Markdown file for the given slug and locale. Candidate order:

  1. website/i18n/{locale}/docusaurus-plugin-content-docs/current/{slug}.md
  2. docs/user/{slug}.md (English fallback)

Returns { content, data } from gray-matter. The English fallback is intentional for legal text — an outdated translation is worse than none. The licenses pages bypass this by always passing "en".

MarkdownPagesrc/lib/ui/MarkdownPage.tsx

Shared component that renders Markdown content using react-markdown with:

  • remark-gfm — GitHub Flavored Markdown (tables, strikethrough, task lists)
  • rehype-raw — passes raw HTML nodes through instead of stripping them
  • Custom table, th, td component overrides that inject borderCollapse: "collapse" and border: "1px solid #d1d5db" styles, making table borders visible without relying on the host page's CSS

Next.js file tracing

next.config.mjs includes outputFileTracingIncludes at the top level (not under experimental):

outputFileTracingIncludes: {
"/*": ["./docs/user/**/*.md", "./website/i18n/**/*.md"],
},

This ensures Vercel bundles the Markdown files into the deployment so they are available at build time when generateStaticParams renders the pages.

Turbopack NFT warning

During next build, Turbopack emits one warning:

Encountered unexpected file in NFT list
Import trace: ./next.config.mjs → ./src/lib/markdown/loadDoc.ts → ./src/app/[locale]/(legal)/about/page.tsx

This is a known Turbopack limitation. The NFT tracer runs at compile time and finds fs.readFileSync(path.join(process.cwd(), ...)) in loadDoc.ts. Because process.cwd() is a runtime value, Turbopack cannot statically scope the file access and traces the entire project root, which includes next.config.mjs. The /*turbopackIgnore: true*/ hint in loadDoc.ts does not suppress this because it only affects dynamic require()/import() resolution — not fs operations.

The warning is cosmetic: the build succeeds, all 10 × 4 = 40 SSG pages are generated correctly, and the deployment works.


Docusaurus side

format: 'detect' in website/docusaurus.config.ts

Docusaurus v3 parses .md files as MDX by default. MDX does not render raw HTML tags — they appear as plain text. Setting format: 'detect' tells Docusaurus to parse .md files as CommonMark and .mdx files as MDX.

Content authoring rules for shared Markdown

Files in docs/user/legal/ are rendered by both react-markdown (app) and Docusaurus. Keep content compatible with both:

  • Plain CommonMark/GFM is always safe.
  • Raw HTML is allowed — rehype-raw handles it in the app, format: 'detect' handles it in Docusaurus.
  • Do not use Docusaurus-specific syntax: admonitions (:::note), MDX/JSX components, or @site/ import paths.
  • Use relative links for cross-references between legal pages (e.g. ./licenses). This resolves correctly in both the Next.js app (relative to the current URL) and in Docusaurus (relative to the source file). Avoid Docusaurus-absolute paths like /docs/legal/... — they break in the app.

Third-party licenses page

docs/user/legal/licenses.md is generated automatically — do not edit it by hand. It is served in the app at /[locale]/licenses.

Generator script

node --import tsx/esm scripts/license-doc.mts
# or via the npm script (if defined):
npm run generate:licenses

The script (scripts/license-doc.mts):

  1. Runs npx license-checker-rseidelsohn --json --production twice: once in the project root and once in website/, to collect license data for all installed packages in both workspaces.
  2. Reads direct production dependencies from both package.json and website/package.json, merging them into a single set.
  3. Deduplicates by package name (first occurrence wins when a package appears in both workspaces) and sorts alphabetically.
  4. Generates a GFM Markdown table with four columns (two packages per row), with :-------: alignment markers on the License columns to center their content.
  5. Writes the result to docs/user/legal/licenses.md with Docusaurus frontmatter.

Re-run the script whenever production dependencies are added, removed, or relicensed.

Table rendering

The generator emits a plain GFM table (pipe syntax). Table borders are not a property of the Markdown — they are applied by the renderer:

  • App (MarkdownPage): custom table/th/td component overrides inject border: "1px solid #d1d5db" and borderCollapse: "collapse" via inline styles.
  • Docusaurus: the Infima CSS theme applies borders to all tables by default.