Skip to content

[codex] add sponsor feed to docs#608

Draft
jdx wants to merge 1 commit intomainfrom
codex/add-sponsors-feed
Draft

[codex] add sponsor feed to docs#608
jdx wants to merge 1 commit intomainfrom
codex/add-sponsors-feed

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Apr 27, 2026

Summary

Adds an en.dev company sponsor block to the docs footer.

Changes

  • Adds EndevSponsors.vue, which fetches https://en.dev/sponsors.json client-side.
  • Renders the feed's default sponsors list, which is paid company sponsors only.
  • Places the sponsor block above the existing en.dev footer.

Validation

  • npm install --no-package-lock --ignore-scripts --legacy-peer-deps
  • npm run docs:build
  • Commit hook also ran the repo's Rust checks/formatting before commit.

Note

Medium Risk
Adds a client-side fetch to an external endpoint (https://en.dev/sponsors.json) and renders third-party-provided logo/link data in the docs layout, which can impact privacy, performance, and reliability if the endpoint is slow or changes.

Overview
Adds a new EndevSponsors footer section to the VitePress docs theme that fetches and displays company sponsor logos/links from https://en.dev/sponsors.json (filtering out infrastructure entries and requiring name/url/logo).

Updates the theme layout to render the sponsors block above the existing EndevFooter at layout-bottom.

Reviewed by Cursor Bugbot for commit 7a79a49. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.03%. Comparing base (577390b) to head (7a79a49).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #608   +/-   ##
=======================================
  Coverage   79.03%   79.03%           
=======================================
  Files          48       48           
  Lines        7235     7235           
  Branches     7235     7235           
=======================================
  Hits         5718     5718           
  Misses       1140     1140           
  Partials      377      377           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new EndevSponsors component to display company sponsors in the footer, fetching data dynamically from an external JSON source. Feedback includes improving list rendering stability by using unique keys, adhering to SEO best practices with the rel="sponsored" attribute, optimizing image loading for performance, and converting the component to TypeScript for consistency with the rest of the project.

<div class="EndevSponsorsLogos">
<a
v-for="sponsor in sponsors"
:key="sponsor.name"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using sponsor.name as a key can be problematic if multiple sponsors share the same name. It is safer to use sponsor.url or a unique ID from the payload to ensure stable rendering and avoid potential key collisions.

          :key="sponsor.url"

:aria-label="sponsor.name"
class="EndevSponsorsLogo"
:href="sponsor.url"
rel="noopener noreferrer"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For links to sponsored content, search engines recommend using rel="sponsored". This helps in adhering to SEO best practices for paid or compensated links.

          rel="noopener noreferrer sponsored"

rel="noopener noreferrer"
target="_blank"
>
<img :alt="sponsor.name" :src="sponsor.logo" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Consider adding loading="lazy" and decoding="async" to the sponsor logo images. Since this component is located in the footer, deferring the loading of these images can improve the initial page load performance.

          <img :alt="sponsor.name" :src="sponsor.logo" loading="lazy" decoding="async" />

Comment on lines +31 to +54
<script setup>
import { onMounted, ref } from "vue";

const sponsors = ref([]);

onMounted(async () => {
try {
const res = await fetch("https://en.dev/sponsors.json", {
headers: { Accept: "application/json" },
});
if (!res.ok) return;

const payload = await res.json();
sponsors.value = (Array.isArray(payload.sponsors) ? payload.sponsors : [])
.filter((sponsor) =>
sponsor?.kind !== "infrastructure" &&
sponsor?.name &&
sponsor?.url &&
sponsor?.logo
);
} catch {
sponsors.value = [];
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The project uses TypeScript in other theme files (e.g., index.ts). To maintain consistency and leverage type safety, consider using <script setup lang="ts"> and defining an interface for the sponsor data structure.

<script setup lang="ts">
import { onMounted, ref } from "vue";

interface Sponsor {
  name: string;
  url: string;
  logo: string;
  kind?: string;
}

const sponsors = ref<Sponsor[]>([]);

onMounted(async () => {
  try {
    const res = await fetch("https://en.dev/sponsors.json", {
      headers: { Accept: "application/json" },
    });
    if (!res.ok) return;

    const payload = await res.json();
    const rawSponsors = Array.isArray(payload.sponsors) ? payload.sponsors : [];
    sponsors.value = rawSponsors.filter((sponsor: any) =>
      sponsor?.kind !== "infrastructure" &&
      sponsor?.name &&
      sponsor?.url &&
      sponsor?.logo
    );
  } catch {
    sponsors.value = [];
  }
});

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

Adds EndevSponsors.vue, a client-side component that fetches https://en.dev/sponsors.json and renders paid company sponsor logos above the existing footer. The integration in index.ts is straightforward.

  • P1: sponsor.url from the external feed is used as an anchor href with only a truthiness check; a javascript: value would pass and execute XSS if the feed is ever compromised. A scheme allowlist (http:/https:) should be enforced before binding the URL.

Confidence Score: 3/5

Not safe to merge without URL scheme validation — a compromised feed could result in XSS on the docs site.

A P1 security finding (unvalidated external URL used as href) is present in the primary new file. While the risk depends on a compromised external endpoint, the fix is trivial and the pattern should be corrected before shipping.

docs/.vitepress/theme/EndevSponsors.vue — specifically the href binding on line 13.

Security Review

  • XSS via unvalidated href (EndevSponsors.vue line 13): sponsor.url is sourced from an external JSON endpoint and bound directly to an anchor's href. A javascript: URI in the feed would execute arbitrary JavaScript in the visitor's browser. URL scheme validation (http:/https: allow-list) is missing.

Important Files Changed

Filename Overview
docs/.vitepress/theme/EndevSponsors.vue New component that fetches and renders sponsor logos from an external JSON feed; has an unvalidated external URL used as href (P1 security) and missing image dimensions (P2 CLS).
docs/.vitepress/theme/index.ts Imports EndevSponsors and slots it before EndevFooter in layout-bottom; change is minimal and correct.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant VitePress as VitePress Layout
    participant EndevSponsors as EndevSponsors.vue
    participant API as en.dev/sponsors.json

    Browser->>VitePress: Page load
    VitePress->>EndevSponsors: Render (layout-bottom slot)
    Note over EndevSponsors: sponsors = [] → section hidden
    EndevSponsors-->>Browser: (nothing rendered yet)
    EndevSponsors->>API: fetch() on onMounted
    API-->>EndevSponsors: { sponsors: [...] }
    Note over EndevSponsors: filter out infrastructure,<br/>require name + url + logo
    EndevSponsors-->>Browser: Render sponsor logos + CTA
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "add sponsor feed to docs" | Re-trigger Greptile

</p>
<div class="EndevSponsorsLogos">
<a
v-for="sponsor in sponsors"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Unvalidated external URL used as href

sponsor.url is inserted directly into the anchor's href with only a truthiness check. A javascript:… value would pass that check and execute in the visitor's browser if the en.dev/sponsors.json feed is ever compromised or returns unexpected data. The existing EndevFooter.vue hardcodes its URL precisely to avoid this, so the same level of safety should apply here.

Suggested change
v-for="sponsor in sponsors"
:href="sanitizeUrl(sponsor.url)"

And add a helper in <script setup>:

function sanitizeUrl(url) {
  try {
    const { protocol } = new URL(url);
    return protocol === 'https:' || protocol === 'http:' ? url : '#';
  } catch {
    return '#';
  }
}

Fix in Claude Code

rel="noopener noreferrer"
target="_blank"
>
<img :alt="sponsor.name" :src="sponsor.logo" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Missing width/height on sponsor <img> causes CLS

The existing EndevFooter.vue sets explicit width and height on its image to prevent layout shifts. These sponsor logos lack dimensions, so every page load that renders this block will produce a measurable Cumulative Layout Shift while the images load.

Suggested change
<img :alt="sponsor.name" :src="sponsor.logo" />
<img :alt="sponsor.name" :src="sponsor.logo" width="120" height="22" />

Fix in Claude Code

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.

1 participant