Date assessed: 24 July 2026
The documentation sources are heavily dependent on Hugo today, but most content-level lock-in is concentrated in a few conventions that can be replaced without changing the published experience.
The highest-value changes are:
- Replace
relrefcalls with ordinary Markdown links and resolve or validate them with a link render hook. - Replace note, warning, tip, and alert shortcodes with Markdown blockquote alerts and a blockquote render hook.
- Replace the image shortcode with Markdown images and an image render hook.
- Replace code-related shortcodes with fenced code blocks.
- Use table render hooks for responsive Markdown tables.
These changes would move Hugo-specific behavior out of thousands of content files and into a small rendering adapter. The equivalent adapter could later be implemented for another site generator.
Some shortcodes are not merely presentational. Features such as generated multi-client examples, content transclusion, child-page tables, and data-driven command lists require preprocessing or an equivalent data and page-model API. Render hooks are not an appropriate replacement for these.
This assessment covers the current repository checkout and:
- 5,146 Markdown files under
content/ - Hugo v0.143.1
- 41 shortcode templates
- 6 existing render hooks
- 153 layout files
Shortcode calls and affected files were counted across the Markdown sources. Front matter, content organization, raw HTML, Hugo configuration, layout templates, data access, alternate output formats, and build scripts were also reviewed.
| Construct | Instances | Files affected | Assessment |
|---|---|---|---|
relref |
26,856 | 3,524 | Largest dependency; readily replaceable |
| Note, warning, tip, info, and alert shortcodes | 2,960 | 1,290 | Readily replaceable with blockquote alerts |
image |
1,648 | 445 | Mostly replaceable with Markdown images |
highlight, code, and redis-cli |
749 | 340 | Mostly replaceable with fenced code blocks |
multitabs |
624 | 585 | Needs a different progressive-enhancement design |
clients-example |
564 | 109 | Data-generation feature |
embed-md |
299 | 207 | Content transclusion; no standard Markdown equivalent |
embed-yaml |
136 | 32 | Better handled by preprocessing |
table-children |
94 | 89 | Depends on Hugo's page tree |
| Other specialized shortcodes | 409 | — | Mixed presentation and data-generation features |
Overall:
- 3,932 files, approximately 76%, contain at least one shortcode, including
relref. - 2,297 files, approximately 45%, contain at least one custom, non-built-in shortcode.
- Replacing links, callouts, images, and code wrappers would remove about 94% of all shortcode invocations.
- After those migrations, approximately 1,098 files and 2,126 genuinely custom shortcode calls would remain.
| Current convention | Preferred source form | Hugo implementation |
|---|---|---|
[text]({{< relref "…" >}}) |
Ordinary Markdown link | Link render hook resolves and validates it |
{{< image … >}} |
 |
Image render hook adds lightbox and link behavior |
note, warning, tip, info, alert |
> [!NOTE], > [!WARNING], etc. |
Blockquote render hook produces the existing alert markup |
highlight, code |
Fenced code block | Default renderer or code-block hook |
redis-cli |
A redis-cli fenced code block |
Code-block hook adds terminal presentation |
table-scrollable |
Ordinary Markdown table | Table render hook adds a responsive wrapper |
definition |
Markdown definition list | Goldmark definition-list support |
| Diagrams and interactive checklists | Existing typed code fences | Retain the current render-hook pattern |
Hugo supports render hooks for links, images, blockquotes, code blocks, headings, passthrough elements, and tables. These hooks are well suited to presentation changes applied to otherwise meaningful Markdown.
See the Hugo render-hook documentation.
Use ordinary source-relative Markdown links as the canonical form:
[Transactions](../using-commands/transactions.md)A Hugo link render hook can:
- resolve the source file to its published permalink;
- retain the current base-URL behavior;
- distinguish internal, external, and fragment-only links;
- report unresolved pages during the build.
An independent Markdown link checker should also run in CI so that link correctness does not itself depend on Hugo.
Source-relative links are preferable to versioned site URLs because they work in repository viewers and generic Markdown tooling without coupling the source to a particular deployment prefix.
A link render hook was prototyped and tested (DOC-6909). It is committed at
layouts/_default/_markup/render-link.html.
The method: add the hook, convert every relref in content/develop/clients/
(1,072 calls across 110 files) to plain Markdown links, build, and diff
rendered link targets against a relref baseline.
Parity is exact. Across 125 client pages, every internal link resolved
byte-for-byte identically to relref, including anchors, mixed-case paths,
bare page-relative paths, and .md suffixes. The only remaining differences
were cosmetic percent-encoding of literal parentheses in external URLs
(( becomes %28), which Goldmark applies to the destination it hands the
hook.
relref and plain links can coexist. They do not have to be migrated in a
single pass. When a relref is still present its destination is Hugo's internal
shortcode placeholder at hook time; the shortcode expands afterwards and
substitutes the real URL back in — even inside the href the hook emitted. The
hook therefore includes a transition guard: if the destination still contains a
shortcode placeholder, it is passed through untouched and no warning is emitted.
Without that guard, an in-progress migration logs one spurious "unresolved"
warning per un-migrated relref (26,847 in this repository), which would bury
the genuine broken-link warnings. Remove the guard once every relref has been
migrated.
Internal links are not only content pages. The use-case demos link to
companion source files that ship in the page bundle, e.g. [source](cache.rs).
These are page resources, not pages, so GetPage cannot see them; the hook
resolves them with .Page.Resources.GetMatch before reporting a link as
unresolved. With that in place the remaining build warnings are mostly genuine
signal — real dead links, alias/redirect targets that GetPage cannot resolve,
and static-directory files.
Installing the hook is the atomic event, not the content migration. The
hook is global, so on the day it lands it reprocesses every plain Markdown link
that already exists in the repository — for example the reply-type links in the
command reference pages, which are authored as ../../develop/... relative
links, not relref. For those pre-existing links the hook applies harmless
normalisation (relative to absolute, .md stripped, trailing slash added); the
targets are unchanged. Content conversion can then proceed gradually, but the
hook itself must be parity-tested against the whole site, not only against the
pages being migrated.
A hook must be as robust as Hugo's built-in renderer. Testing surfaced four defects that only appear at corpus scale, each of which silently dropped pages or failed the build:
.Page.File.Pathpanics with a nil-pointer dereference on pages that have no backing file — content generated viamarkdownify(the command pages) and shortcode inner content (note,alert). Use.Page.Pathinstead.urls.Parsehard-errors on a malformed destination (a pre-existing[Authority]([Authority](https://...))link) and fails the entire build. Detect external links with afindREscheme match instead.- The unresolved-link fallback duplicated the fragment (
#cas#cas) because it re-appended an anchor that the destination already contained. GetPagecannot resolve alias (redirect-stub) targets, so links to aliased paths produce false "unresolved" warnings even though the output is correct.
Before a migration, run the hook site-wide and fix pre-existing malformed links, which a hook converts from silently-wrong output into hard build failures.
To check that parity was not specific to one section, the conversion was
repeated across four structurally different areas at once — 825 files and about
5,000 relref calls — each chosen to exercise a distinct feature:
| Section | Feature exercised |
|---|---|
operate/rs/databases/active-active |
Content mounted under two URL paths |
operate/rc |
Image-heavy pages; the target of that mount |
operate/kubernetes |
Mixed-case paths and generated API-reference pages |
integrate |
Cross-tree links |
The build produced no errors, dropped no pages, and added no new warnings.
Every rendered-link difference against the relref baseline was benign
normalisation (relative to absolute, .md stripped, trailing slash added).
The mounted Active-Active tree is the most demanding case, because the same
source file is published under both /operate/rs/… and /operate/rc/…. The
hook resolves each relative link to the mount-appropriate permalink — for
example develop/data-types renders as
/operate/rs/databases/active-active/develop/data-types/ under the Software
path and /operate/rc/databases/active-active/develop/data-types/ under the
Cloud path — matching relref exactly on both. This relies on resolving with
.PageInner.
A versioned tree (operate/rs/7.8, 326 files) was converted separately. All
435 of its pages carry a url: front-matter override, and GetPage's
.RelPermalink honours those overrides identically to relref: parity was
exact apart from the cosmetic external-parenthesis encoding. This pass also
surfaced and fixed the last render-hook edge case — a malformed link whose
anchor contained an embedded URL with its own #. Composing the resolved href
without safeURL made Go's template autoescaper blank it to ZgotmplZ, and
splitting the anchor on every # truncated it. The hook now applies safeURL
to the composed URL and splits on the first # only, so such anchors are
preserved intact and match the baseline.
Version-specific links also interact with the archiving tool described below.
Several build and authoring tools treat relref as a literal string — they
parse or generate the shortcode directly. These must be updated or retired as
part of the migration, or they will silently produce wrong output:
build/version_archiver.pyrewrites{{< relref "/…" >}}with a regular expression to make links version-specific when a versioned documentation snapshot is created. Against plain Markdown links it matches nothing, so archived versions would keep unversioned links.build/redisvl_docs_sync.pyis an importer that emitsrelrefsyntax when converting upstream RedisVL documentation. It would need to emit plain Markdown links instead..claude/hooks/check_shortcode_paths.pyvalidatesrelreftarget paths on edit. Once links are plain Markdown, that validation moves to the render hook and an independent link checker.layouts/partials/process-markdown-content.htmlregex-replacesrelrefwhen generating the Markdown and JSON outputs (see "Current alternate-output fragility" below); standard links would let the render hook handle this instead.
An audit for relref used as a literal string across build/, layouts/, and
.claude/ should be part of migration planning.
Replace callout shortcodes with GitHub-style blockquote alerts:
> [!WARNING]
> Back up the database before continuing.Hugo v0.143.1 supports the necessary blockquote render hooks and alert metadata. Unsupported Markdown processors still display the content as a normal blockquote.
A blockquote render hook is prototyped at
layouts/_default/_markup/render-blockquote.html.
It renders > [!NOTE] / > [!WARNING] / > [!TIP] / > [!INFO] and similar
alerts with the same styling as the current note/warning/tip/info/alert
shortcodes, and leaves regular blockquotes rendering exactly as before
(verified byte-identical). Crucially, an alert body is native Markdown, so its
links are rendered in the page's context and resolve correctly — unlike the
shortcodes, which markdownify their inner content in a page-less context and
so break relative links. This makes the blockquote migration a prerequisite
for portable, source-relative links inside callouts, not merely a cosmetic
change.
Migration should use a Markdown- or shortcode-aware parser rather than regular expressions. At least 544 existing callouts contain nested shortcodes, and some callout bodies are very large. Inner shortcodes should be migrated before their enclosing callout where possible.
Approximately 1,093 of the 1,648 image calls use only a filename and optional alt text. These can be converted directly:
An image render hook can retain:
- the link to the full image;
- lightbox behavior;
- URL normalization;
- the existing
#no-clickconvention, if it is still required; - optional titles and semantic styling.
The remaining image calls use width or class. The preferred options are:
- Remove unnecessary per-image sizing through responsive CSS.
- Replace arbitrary classes with a small set of semantic styles such as
inline-icon,small, orwide. - Use Goldmark image attributes only for genuine exceptions.
Goldmark attributes are not CommonMark, but they degrade more gracefully than Hugo shortcode calls. Arbitrary Tailwind class strings should not form part of the long-term content schema.
There is also an accessibility opportunity: 418 image shortcode calls do not currently specify alt text.
The current image shortcode declares a default width of 75%, but does not
apply it. Only explicitly provided widths appear in the rendered img
element. See layouts/shortcodes/image.html.
Replace highlight and code shortcodes with normal fenced code blocks.
Replace redis-cli with a redis-cli fenced block and use a code-block render
hook to add the current terminal chrome.
A table render hook can wrap ordinary Markdown tables in a responsive
overflow container. This removes the need for the table-scrollable
shortcode without sacrificing the current HTML behavior.
The repository already follows this progressive-enhancement approach for
Mermaid diagrams, checklists, hierarchies, decision trees, and timelines. See
for-ais-only/render_hook_docs/README.md.
There is no standard Markdown representation for tabs. The best portable source is ordinary titled sections:
### RESP2
RESP2 return information.
### RESP3
RESP3 return information.JavaScript can progressively enhance recognized groups into tabs. Other renderers will show the sections sequentially, which is a useful and accessible fallback.
Of the 624 multitabs calls, 534 are in command-reference pages. Their
generator should emit RESP headings directly instead of emitting shortcode
syntax. Arbitrary tab sets could use a generator-neutral marker, but such a
marker would still be a custom extension rather than standard Markdown.
The following features generate or transclude content rather than simply altering Markdown presentation:
clients-examplejoins generated example data, client configuration, source files, and command metadata.jupyter-examplereads source content and configures interactive notebook behavior.embed-mdtranscludes another page through Hugo's page API.embed-yaml,embed-code, andcode-includeread files during rendering.table-childrenqueries Hugo's page tree and child-page front matter.command-groupgenerates command lists from data files.rc-supported-regions,table-csv,external-json, and similar shortcodes generate content from local or remote data.
These should be moved to a generator-neutral preprocessing stage:
authored Markdown + manifests + shared fragments
|
v
repository build tools
|
v
fully expanded Markdown tree
|
v
Hugo or another site generator
The expanded Markdown tree becomes a portable intermediate representation. Hugo and any future generator can consume the same tree. The authoring layer may retain a small project-specific directive or manifest schema, but it no longer depends on Go templates or Hugo's page APIs.
Removing shortcodes would not by itself make the complete site generator-independent. The repository also relies on:
- 992
_index.mdfiles for section pages and hierarchy; - front matter fields such as
weight,alwaysopen,hideListLinks,url,aliases,type,layout, andcascade; - 701 alias declarations;
- page-tree navigation and sorting;
- Hugo's data directory and site configuration;
- Hugo's asset pipeline;
- related-content, taxonomy, menu, and template APIs;
- alternate Markdown and JSON output formats;
- a Hugo module mount that exposes the same Active-Active source under both Redis Software and Redis Cloud paths.
YAML front matter is broadly supported by documentation generators, but the project should define its own metadata schema. Generator adapters can then map that schema to Hugo or a future system.
The content mount is configured in config.toml. A portable
replacement would materialize the duplicated tree during preprocessing or
model the shared content explicitly in a manifest.
An approximate scan found recognized raw HTML elements outside code fences in 1,819 Markdown files. Much of the volume is generated table and REST API markup, including:
- tables and table cells;
detailsandsummary;span,br, andnobr;- raw links and images;
- formatting elements.
Raw HTML is not Hugo-specific, and many Markdown systems support it, but its
security policy and styling vary between renderers. The current repository
explicitly enables unsafe Goldmark rendering in config.toml.
Raw HTML should therefore be treated as a secondary portability workstream.
Generated tables, <br>, <span>, <nobr>, and manually constructed
<details> blocks are the best initial targets.
The Markdown and JSON output pipeline already has to recreate Hugo shortcode behavior through regular-expression replacements. It explicitly handles a subset of constructs, repeatedly unescapes HTML entities, and finally removes all remaining shortcode tags.
See
layouts/partials/process-markdown-content.html.
This is evidence of the maintenance cost of storing presentation macros in the source. Standardizing source Markdown would simplify HTML rendering, AI-facing Markdown, JSON generation, indexing, and future migrations at the same time.
- Define canonical conventions for links, images, callouts, code blocks, and tables.
- Add link, image, blockquote, and table render hooks.
- Add generator-independent link and content linting to CI.
- Prevent new uses of replaceable shortcodes.
- Migrate
relrefcalls to normal Markdown links. - Migrate callouts after converting their nested shortcodes.
- Convert simple image calls and address missing alt text.
- Convert code wrappers to fenced code blocks.
- Remove
table-scrollablethrough the table hook. - Update importers and generators so they emit the new conventions.
- Change generated RESP tabs to ordinary headings.
- Progressively enhance appropriate heading groups into tabs.
- Replace small presentational shortcodes with semantic Markdown or HTML.
- Continue using typed code fences for diagrams and structured interactive content.
- Define a generator-neutral schema for includes and data-driven components.
- Expand those components into a portable Markdown build tree before Hugo runs.
- Move mounted or duplicated content into the same preprocessing layer.
- Define and document the portable front matter schema.
- Feed the expanded Markdown tree to a second renderer in CI.
- Compare page counts, resolved links, headings, metadata, and essential semantic content.
- Treat visual parity as an adapter concern rather than an authoring-format concern.
The repository is strongly tied to Hugo as a complete publishing system, but the authoring format can be made substantially more portable without replacing Hugo.
The immediate goal should be:
Store semantic, readable Markdown in
content/, and use Hugo only as a rendering and publishing adapter.
Render hooks provide a practical route for links, images, callouts, code blocks, tables, and structured fenced blocks. True content-generation features should be isolated behind a preprocessing boundary. Together, these changes would reduce the cost and risk of evaluating another documentation platform while preserving the existing Hugo site.