Add CUSTOMIZATIONS.md documenting everything added over base Docus
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
# Customizations over base Docus
|
||||||
|
|
||||||
|
This project starts from the `docus` i18n starter template (`extends: ['docus']` in `nuxt.config.ts`, Docus v5.x on Nuxt ^4.4.8). This file tracks everything added or changed on top of that base, and *why*, so a future contributor doesn't have to diff `node_modules/docus` to find out.
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
|
||||||
|
- **`better-sqlite3`** — Nuxt Content v3 stores all parsed markdown content in a local SQLite database (`.data/content/contents.sqlite`) using its own DB layer (`db0`) rather than reading files at request time. `db0` needs an actual SQLite driver to talk to that file, and lists `better-sqlite3` as a *peer* dependency (alongside alternatives like `sqlite3` or `@libsql/client`) — peer dependencies aren't auto-installed by npm, so without declaring it explicitly, `@nuxt/content` has no driver to write to and the local content database silently fails to build.
|
||||||
|
- **`@nuxtjs/i18n`** module added explicitly in `nuxt.config.ts`. The starter ships an i18n-*shaped* content structure (`content/en/`, `content/fr/`) out of the box, but that's just a folder convention — nothing routes `/fr/...` URLs, switches locales, or auto-detects the browser's language unless the module itself is registered.
|
||||||
|
|
||||||
|
## `nuxt.config.ts`
|
||||||
|
|
||||||
|
- **Git-based page contributors.** `getContributors()` runs `git log --format=%an --follow -- <file>` for each markdown file and dedupes the author list, injected into the page's content via the `content:file:afterParse` hook. This was chosen over the Gitea/GitHub API because it needs no access token, no network call, and no rate limiting — the info is already in the checkout. The trade-off: CI must do a **full** (non-shallow) `git checkout`, otherwise `git log` only sees one commit per file and every page shows just its most recent author instead of everyone who ever touched it.
|
||||||
|
- **`@nuxt/image` dev/prod path branch.** `@nuxt/image` resolves where to read local files from differently depending on context: in `nuxi dev` it needs an absolute filesystem path to `public/`, but in a production build it needs the plain relative string `'public/'` — passing the absolute path there breaks the `Content-Type` header on the built `/_ipx` image-proxy route specifically for SVGs (they'd get served with the wrong MIME type). The `isDev` check branches on the actual `nuxi` subcommand so both environments get the value they each require.
|
||||||
|
- **Custom icon collection.** `icon.customCollections` registers a `brand` prefix pointing at `app/assets/brand-icons/`, so logos for the user's other projects (Instameex, Lumeex) can be referenced from content as `i-brand-instameex` etc., exactly like any Iconify icon — without needing to publish them to an actual Iconify icon set first.
|
||||||
|
- **Markdown highlight.** Forces the `github-dark` Shiki theme for *both* the light and dark slots, because the site never actually offers a light mode (see `docus.colorMode: 'dark'` below) — maintaining two highlight themes for a mode nobody sees would just be dead config. The extra languages (`nginx, properties, php, toml, console, sh, yaml`) were added because the tutorial content includes config-file snippets and terminal output in all of these syntaxes, and none of them are in Shiki's minimal default bundle for Nuxt Content.
|
||||||
|
- **`darkreader-lock` meta tag.** The Dark Reader browser extension rewrites elements' inline `style` attributes on the client, after Nuxt has already server-rendered them — so any component using an inline `style` (like the cyan "·" separator spans) ends up with mismatched HTML between server and client, and Vue logs a hydration-mismatch warning on every page load for any visitor running that extension. This meta tag is Dark Reader's own opt-out signal, telling the extension to leave the page alone instead of trying to work around the mismatch after the fact.
|
||||||
|
- **301 redirects (`routeRules`).** The old site served French content at root-level URLs (e.g. `/generalites/reseau/nat`, no locale prefix, on a separate `french` git branch). Restructuring into a single repo with `@nuxtjs/i18n`'s `/fr/...` prefix changed every French URL, which would otherwise break external links, bookmarks, and search-engine rankings for those pages built up over time. All 44 mappings use `statusCode: 301` explicitly — Nitro's default redirect status is 307 (temporary), which search engines don't treat as "please re-index this at the new URL" the way a 301 (permanent) does. There's deliberately **no** `/` → `/fr` redirect: root already serves English by default, and `@nuxtjs/i18n`'s `detectBrowserLanguage` already handles sending French-browser visitors to `/fr` automatically — a static redirect rule would just fight with that.
|
||||||
|
|
||||||
|
## `content.config.ts`
|
||||||
|
|
||||||
|
Nuxt Content validates every page's frontmatter against a Zod schema per collection, and **silently drops any key that isn't declared in that schema** — it doesn't error, the field just isn't there at render time. This file reimplements docus's own `createDocsSchema()` (not something the `docus` package actually exports, so it has to be copied rather than imported) and extends it with the custom frontmatter toggles the page template relies on:
|
||||||
|
|
||||||
|
- `hideHeader` — skip the title/description block on a page (used for pages that want a custom hero instead of the standard header).
|
||||||
|
- `hideCopyPage` — hide the "Copy page" button group (for pages where "copy as markdown for an LLM" doesn't make sense).
|
||||||
|
- `hideToc` — hide the right-hand table of contents (for short pages where a TOC would be mostly empty space).
|
||||||
|
- `contributors` — the array populated by the `getContributors()` hook above; without this line in the schema, the hook's output would be computed and then thrown away.
|
||||||
|
|
||||||
|
This was a real bug during development: `hideHeader`/`hideCopyPage` did nothing at all until this schema was extended, because the fields were being stripped before the page component ever saw them.
|
||||||
|
|
||||||
|
## `app/app.config.ts`
|
||||||
|
|
||||||
|
The old production site (`docu.djeex.fr`) has an established visual identity that a "generic Nuxt UI theme" migration would have lost. These overrides were measured directly against the live old site (colors picked from its actual computed styles, not eyeballed) so the new stack keeps the same look rather than just being *a* documentation theme:
|
||||||
|
|
||||||
|
- `docus.colorMode: 'dark'` — the old site never had a light mode either; hard-locking it here removes the need for the toggle UI and light-theme variants entirely, rather than half-supporting a mode nobody uses.
|
||||||
|
- `ui.colors`: primary `cyan`, neutral `zinc` — the site's brand accent color and its neutral gray scale.
|
||||||
|
- `ui.prose.card` / `ui.prose.pre` / `ui.header` / `ui.contentSearchButton` / `ui.contentSurround` / `ui.kbd`: exact background/border hex values (a shared `rgba(12,13,12,0.8)` translucent-dark family, e.g. `#121110` borders) matching the old site's card, code-block, header, search button, and prev/next-link chrome, since Nuxt UI's defaults use a different neutral scale that didn't match.
|
||||||
|
- `ui.prose.callout.compoundVariants`: exact colors for all four admonition severities (info/success/warning/error), overriding Nuxt UI's default callout palette so `::note`, `::tip`, `::warning`, `::caution` render in the same colors the old site's `::alert` boxes used, rather than Nuxt UI's stock blue/green/amber/red.
|
||||||
|
|
||||||
|
## Custom / overridden components (`app/components/`)
|
||||||
|
|
||||||
|
Nuxt's convention is that a file at `app/components/<any-subfolder>/<ExactComponentName>.vue` overrides a layer's (here, docus's) auto-registered component of the same name — no explicit registration needed, just matching the filename. Each one below was diffed against the actual stock file in `node_modules/docus` to confirm it's a real, deliberate change and not an accidental untouched copy:
|
||||||
|
|
||||||
|
- **`app/AppHeader.vue`** — added a Gitea social icon link alongside the stock GitHub link. The project's canonical repository lives on the user's self-hosted Gitea instance; GitHub is only a mirror, so a GitHub-only link would point visitors to the secondary copy.
|
||||||
|
- **`app/AppHeaderCenter.vue`** — the most heavily rewritten component. Stock Docus sizes the header's nav menu to the header's own container width, but this site's actual docs pages use a narrower, off-center content column (a two-level 10-column grid: an outer sidebar column plus an inner article/TOC split) — so the stock menu didn't visually line up under the content it was supposed to sit above. This override renders the nav as an absolutely-positioned overlay that replicates that exact two-level grid, so it lines up with the real article column instead of the header's own slot. Also fixes a real bug found during development: `pointer-events-auto` was originally applied to the full-width wrapper div, which silently blocked clicks on the logo and the right-side icons (search, color mode, socials) everywhere *except* the homepage (a different code path with an empty nav). It's now scoped to only the innermost column div that actually contains clickable content.
|
||||||
|
- **`app/AppHeaderBottom.vue`** — emptied to a no-op `<div />`. Once navigation moved into `AppHeaderCenter` above, the stock second nav row would have shown the same links twice and wasted vertical space in the header.
|
||||||
|
- **`docs/DocsAsideLeftBody.vue`** — the left doc-tree sidebar is now collapsible and closed by default (stock: always fully expanded, not collapsible). With this site's number of nested sections, a fully-expanded tree was one very long scrollable list on every page load; collapsed-by-default lets a visitor see the top-level structure first and open only the section they need.
|
||||||
|
- **`docs/DocsAsideLeftTop.vue`** — added a full-width search button above the sidebar for the header-based subnav mode (stock rendered nothing there in that mode, only in the "aside" subnav mode). Without it, visitors on pages using header-mode subnav had no visible way to open search from the sidebar area at all.
|
||||||
|
- **`docs/DocsPageHeaderLinks.vue`** — gave the "Copy page" button group the same translucent-dark card styling used everywhere else on the site. Purely cosmetic: the stock Nuxt UI button styling didn't match the rest of the page chrome and stood out as an unstyled default.
|
||||||
|
- **`prose/ProseNote.vue`, `ProseTip.vue`, `ProseWarning.vue`, `ProseCaution.vue`** (new files, no stock equivalent to override against — these are thin wrappers around Nuxt UI's own `Callout.vue`). Nuxt UI's admonition icon is normally set once, globally, per icon slot — there's no built-in way to omit it on just one specific admonition without changing it for every admonition of that type site-wide. These wrappers read an optional `icon` prop so a single instance can hide its icon (`::note{icon=""}`) when the emoji or leading text already conveys the same meaning, while every other `::note` on the site keeps its default icon.
|
||||||
|
- **`content/Ellipsis.vue`** (new; no Docus or Nuxt UI equivalent exists at all). The old site had a decorative blurred gradient glow behind section headers, and reproducing the content 1:1 meant this cosmetic effect needed *some* markdown-usable component to exist, since neither Docus nor Nuxt UI ships anything similar. Registered as the inline MDC component `:ellipsis{left= width= top= blur= zIndex=}`, used across content wherever the old site had that effect.
|
||||||
|
|
||||||
|
## Page-level features (`app/pages/[[lang]]/[...slug].vue`)
|
||||||
|
|
||||||
|
This catch-all page isn't a docus override (docus doesn't ship one to override — this project defines its own), but it layers frontmatter-driven behavior on top of stock Nuxt Content rendering:
|
||||||
|
|
||||||
|
- `hideHeader` / `hideCopyPage` / `hideToc` — read the three frontmatter toggles declared in `content.config.ts` above and conditionally skip rendering each block.
|
||||||
|
- **Contributors + history block.** Below the "Edit this page" / "Report an issue" links, renders "Contributor(s): <names>" from the `contributors` frontmatter field (populated by the git-log hook), with the names linking to that specific file's Gitea commit history. The goal is to give credit to everyone who's worked on a page — not just whoever last edited it — and let a reader jump straight to the full history of a page without leaving the site or knowing the underlying file path.
|
||||||
|
|
||||||
|
## Other repo-root additions
|
||||||
|
|
||||||
|
- `scripts/mapping-en.py`, `scripts/convert-content.py` — one-off scripts written for the original migration of this site's content onto this stack (mapping old slugs/frontmatter to the new structure, and converting MDC syntax). Kept as a reference for how that migration was done rather than deleted, but they're not part of the running app and aren't invoked by any build step.
|
||||||
Reference in New Issue
Block a user