When you embed @rwdocs/viewer (or render pages through @rwdocs/core) in a host
application that stores its own comments — for example the Backstage plugin
pair — each comment needs a stable identifier for the page it annotates.
PageMeta.path is the site-root-relative URL of the page. It is convenient,
but it is not stable: remounting or moving a whole section to a different base
path changes the path of every page in that section. If your comments are keyed
on path, a section move orphans all of them even though the content is unchanged.
PageMeta also exposes:
sectionRef— the section's identity, e.g.domain:default/billing.subpath— the page's path relative to its section root, e.g.apifor the page atdomains/billing/apiinside sectiondomains/billing. It is the empty string for a section's own root page, and the full page path for pages that fall outside any explicit section (these report the implicit root section).
The pair (sectionRef, subpath) is your durable comment key. It survives a
whole-section remount: the URL prefix changes, but each page's subpath
(relative to its unchanged section) does not.
const commentKey = `${meta.sectionRef}#${meta.subpath}`;| Change | (sectionRef, subpath) survives? |
|---|---|
Mounting a whole section under a different base URL, its sectionRef unchanged |
✅ Yes |
| Relocating a whole section's directory while its name (last path segment) stays the same | ✅ Yes |
Renaming a section (its sectionRef changes) |
❌ No |
Moving a single page within a section (its subpath changes) |
❌ No |
A fully stable identity that survives section renames and intra-section moves
would require an author-supplied front-matter id (or an engine-assigned durable
slug). That is intentionally out of scope today; (sectionRef, subpath) is the
cheap, high-value first step that covers the common case.
If your host stores its own comments and supplies them through
mountRw({ comments }) (an injected CommentApiClient), you are responsible
for sanitizing each comment's bodyHtml. The viewer renders that field as
trusted HTML — it injects it directly, with no client-side sanitization. The
default rw serve backend sanitizes comment markdown to a restricted CommonMark
subset before it ever reaches the viewer; an injected client bypasses that path
entirely.
Two safe options:
- Render with
renderCommentBodyfrom@rwdocs/core, which produces HTML in the same restricted subset the default backend uses. The Backstage backend plugin already does this. - Omit
bodyHtmland return only the plain-textbody— the viewer renders it as text, with no HTML injection.
Returning unsanitized HTML — or proxying bodyHtml straight from an upstream
store — lets comment authors inject scripts that execute in your page's origin
(stored XSS).