Skip to content

feat: link compared frameworks from the /compare pages#857

Merged
vivek7405 merged 4 commits into
mainfrom
feat/outbound-links-compare
Jul 9, 2026
Merged

feat: link compared frameworks from the /compare pages#857
vivek7405 merged 4 commits into
mainfrom
feat/outbound-links-compare

Conversation

@vivek7405

Copy link
Copy Markdown
Collaborator

Closes #856

What

Adds one outbound link per comparison to the competitor's official site, so the /compare pages cite what they compare (a mild positive SEO signal + credibility/E-E-A-T, and it reads as honest rather than evasive).

How

  • New link frontmatter field on each compare/*.md, threaded through modules/compare/types.ts and both queries.
  • The [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>).
  • Links: nextjs.org, lit.dev, remix.run, astro.build, rubyonrails.org. The Remix 3 page also links its "beta preview" mention to the Remix 3 beta post.
  • All outbound links are follow (genuine editorial citations, not paid/untrusted), open in a new tab, and carry rel="noopener noreferrer".
  • The shared blog/compare markdown renderer (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.
  • Booted the site: every /compare/<slug> renders its outbound eyebrow link with target="_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

  • N/A for framework docs: this is website marketing content only, no packages/* public surface changed. The link field is a website-local frontmatter convention.

@vivek7405 vivek7405 self-assigned this Jul 9, 2026

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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.
  2. 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.
  3. 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.

Comment thread website/app/compare/[slug]/page.ts Outdated
Comment thread website/modules/blog/utils/render-post.ts
Comment thread website/modules/blog/utils/render-post.ts

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 //host were treated as in-place internal navs (no new tab, no rel, no cue). Broadened the check to /^(https?:)?\/\//i and 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.

Comment thread website/modules/blog/utils/render-post.ts

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 vivek7405 marked this pull request as ready for review July 9, 2026 06:20

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

vivek7405 added 4 commits July 9, 2026 13:24
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.
@vivek7405 vivek7405 force-pushed the feat/outbound-links-compare branch from abae1b3 to 9dd189a Compare July 9, 2026 07:54
@vivek7405 vivek7405 merged commit 533fc8f into main Jul 9, 2026
9 of 10 checks passed
@vivek7405 vivek7405 deleted the feat/outbound-links-compare branch July 9, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add outbound links to compared frameworks on /compare pages

1 participant