feat: link compared frameworks from the /compare pages#857
Conversation
vivek7405
left a comment
There was a problem hiding this comment.
First pass over the outbound-link change. The mechanics are sound (link threaded through the frontmatter, types, and both queries; the eyebrow hole is a correctly-unquoted webjs attribute hole; index cards stay plain text so there are no nested anchors), but three things needed fixing before this is ready:
- Both new-tab surfaces skipped the site's own a11y convention. The chrome appends a visually-hidden "(opens in a new tab)" cue (lib/links.ts NEW_TAB) on every external link, and there is a layout test asserting it. The compare eyebrow and the renderer's external links now append the same cue.
- No test shipped for either new behavior. Added SSR/unit coverage for the renderer's external-vs-internal branching (with the internal counterfactual) and the eyebrow link.
- The renderer interpolates the markdown URL into a double-quoted href without escaping quotes. Pre-existing and the source is trusted repo markdown, but this PR widens that surface, so a
"in a URL is now percent-escaped so it cannot break out of the attribute.
All three are fixed in the follow-up commit; tests and check are green.
vivek7405
left a comment
There was a problem hiding this comment.
Second pass, focused on the shared renderer's link edge cases and the test. Two real gaps in the external-vs-internal branch, both fixed; one pre-existing renderer limitation filed as a follow-up; the tests hold up as real counterfactuals.
- The external check was case-sensitive and missed protocol-relative URLs, so
HTTPS://...and//hostwere treated as in-place internal navs (no new tab, no rel, no cue). Broadened the check to/^(https?:)?\/\//iand added an uppercase-scheme test case. - The link regex truncates a URL at the first
)(so a Wikipedia-style..._(language)link loses its tail). Pre-existing, not introduced here, and out of scope for this PR, so filed as #860. - Confirmed the internal-link assertion is a genuine counterfactual and the compare-page test is deterministic (getComparison resolves off import.meta.url, not cwd).
Documented the new-tab behavior in the renderer header. Tests and check green.
vivek7405
left a comment
There was a problem hiding this comment.
Third pass, cross-file consistency and completeness. link is threaded identically through the type and both queries; the [slug] eyebrow ternary is well-formed (empty string falls to plain text, the link branch nests a single anchor with the NEW_TAB cue); the index cards correctly omit the outbound link to avoid a nested anchor; no invariant-11 punctuation in the new prose; no sitemap/metadata surface needs the link. Nothing left to fix.
vivek7405
left a comment
There was a problem hiding this comment.
Reviewed the visible-link change (abae1b3). The eyebrow is back to plain text and the outbound link is now an explicit, underlined "Visit the site" link under the tagline with an aria-hidden arrow and the screen-reader cue. Template is well-formed (href is a normal unquoted attribute hole), no nested anchors, no dead code, and the test's label assertion pins the visible link so a revert to hover-only would fail it. Nothing to fix.
Add one outbound link per comparison to the competitor's official site, rendered as the "WebJs vs <competitor>" header eyebrow on each /compare/<slug> page (nextjs.org, lit.dev, remix.run, astro.build, rubyonrails.org). Relevant, authoritative outbound links are a mild positive SEO signal and, more importantly, make the comparisons read as honest and well-cited rather than evasive. The competitor URL rides a new `link` frontmatter field threaded through the compare types and queries. The Remix 3 page also links its "beta preview" mention to the Remix 3 beta post. Links are follow (genuine editorial citations), open in a new tab, and carry rel="noopener noreferrer". The shared blog/compare markdown renderer now opens absolute-URL links in a new tab while internal links still navigate in place.
Address self-review findings on the outbound-link change: - Append the site's visually-hidden "(opens in a new tab)" cue (the lib/links.ts NEW_TAB convention) to the compare eyebrow link and to external links in the shared markdown renderer, so screen readers announce the tab change. - Percent-escape any double quote in a markdown link URL so it cannot break out of the href attribute. - Add SSR/unit tests: the renderer's external-vs-internal branching (with the internal counterfactual) and the compare page's outbound eyebrow link.
Broaden the new-tab detection in the shared markdown renderer so a case-insensitive scheme (HTTPS://) and a protocol-relative //host URL are classified as external (new tab + rel + a11y cue) instead of an in-place internal nav. Document the behavior in the renderer header and cover the uppercase-scheme case in the test.
The competitor link was the header eyebrow styled with no underline, so it only looked like a link on hover. Move it to an explicit, always- visible "Visit the <competitor> site" link under the tagline: accent color, underlined, with an external-link arrow icon, so a reader knows it is a link while reading. The eyebrow returns to plain text. Update the SSR test to assert the visible, underlined outbound link.
abae1b3 to
9dd189a
Compare
Closes #856
What
Adds one outbound link per comparison to the competitor's official site, so the
/comparepages cite what they compare (a mild positive SEO signal + credibility/E-E-A-T, and it reads as honest rather than evasive).How
linkfrontmatter field on eachcompare/*.md, threaded throughmodules/compare/types.tsand both queries.[slug]page renders the "WebJs vs " header eyebrow as the outbound link (one prominent link per page, above the fold, no body clutter). Index cards are left un-linked to avoid a nested anchor (the whole card is already a link to/compare/<slug>).rel="noopener noreferrer".render-post.ts) now opens absolute-URL links in a new tab; internal/links still navigate in place. This is why the Remix inline beta link opens in a new tab, and it is a small improvement for blog external links too.Verification
webjs check: all checks pass./compare/<slug>renders its outbound eyebrow link withtarget="_blank" rel="noopener noreferrer"; the Remix beta-post inline link is present and new-tab; index cards link only to/compare/<slug>(no nested anchors); the blog still boots and its internal links stay same-tab (0 mis-targeted).Docs / surfaces
packages/*public surface changed. Thelinkfield is a website-local frontmatter convention.