Skip to content

feat: add Carbon Ads Top Bar and Sidebar placements - #9250

Open
mcollina wants to merge 2 commits into
mainfrom
feat/carbon-ads
Open

mcollina wants to merge 2 commits into
mainfrom
feat/carbon-ads

Conversation

@mcollina

@mcollina mcollina commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Description

Adds Carbon Ads, served through the OpenJS Foundation's Carbon project for Node.js, using two formats:

  • Top Bar (CWBI6KJY): full width, below the navigation, on the about, article, blog-post, blog-category, download and download-archive layouts.
  • Sidebar (CWBI6KJU): below the table of contents. It appears on about/article from xl (1280px) and on blog-post from ml (896px). Below those widths it is neither shown nor requested, as the format requires.

The homepage and the error/404 pages never render ad markup, reserve no space and make no ad requests. Layouts opt in explicitly (<WithNavBar ads />, <WithMetaBar ads="article" | "post" />), and ads are off by default. The fallback page layout has no ads.

Brian Edit Start

image

with Banner

image

Brian Edit End

Ads are rendered from Carbon's JSON API directly in the browser, without third-party scripts:

  • apps/site/util/carbonAds.ts: requests ads, validates the untrusted response (required fields, hex colours, HTTPS URLs; anything invalid means no ad), and measures viewability. The view beacon fires once, after at least 50% of the ad has been visible for one continuous second while the tab is visible.
  • apps/site/providers/carbonAdsProvider.tsx: tracks page views by locale and pathname. Each format is requested at most once per page view, so StrictMode, remounts, rerenders, theme changes and hash links never record extra impressions, and a revisited page gets a fresh ad.
  • apps/site/components/CarbonAds: renders both formats. While loading, the slot reserves space; when there is no ad (an empty response, an error or an ad blocker), the slot collapses.
  • MetaBar (ui-components) gains a generic optional footer slot, opt-in and with no ad-specific code.

Staging vs production

Every request includes ignore=yes, so impressions are not counted, unless the build sets NEXT_PUBLIC_CARBON_ADS_PRODUCTION=true. Local development and preview deployments are therefore non-counting by default. The variable is embedded at build time and must only be set for the production deployment. See docs/carbon-ads.md for the configuration and Carbon's launch sequence:

  1. Carbon reviews a staging URL.
  2. Production is enabled.
  3. For seven days Carbon counts traffic but returns empty responses, so the slots render nothing.
  4. Paid ads then start appearing in the same slots.

Learn and the API docs are separate applications and are not affected.

Validation

  • pnpm lint, lint:types (site and ui-components), Prettier and pnpm build pass.
  • Unit tests: site 247/247 and ui-components 45/45 pass. New coverage:
    • validation of the response data
    • fetch failure modes
    • viewability timing: 49%, 999ms, threshold resets, hidden tab, cleanup
    • StrictMode, remounts, A→B→A and locale changes
    • stale responses after navigation
    • Sidebar eligibility
    • the MetaBar footer slot
  • A new e2e spec, tests/e2e/carbon-ads.spec.ts, uses mocked Carbon responses to check:
    • no requests and no markup on the homepage
    • one request per format per page view across client-side navigation
    • no Sidebar request on narrow screens
  • A manual Playwright check against next start, with fixture responses using maximum-length creatives, passed all 238 checks:
    • every layout at 375, 768, 895/897, 1279/1281 and 1600px
    • localized homepages, loaded directly and after client-side navigation
    • light and dark themes
    • empty and blocked responses
    • a short desktop viewport
    • resizing across the breakpoints
    • keyboard focus and horizontal overflow

Note: the e2e spec caught a React Compiler issue that unit tests (which run without the compiler) could not catch. The compiler hoisted the page-view Map/Set out of the useMemo, which shared them across navigations. The objects are now created through a function of the page-view key.

Reviewers might want to check:

  • The Sidebar styling, including the gradient fading into the image, against Carbon's reference CSS.
  • The 96px Top Bar reservation on narrow screens. It fits typical copy, but a maximum-length description is about 137–157px tall and causes a small layout shift.

Related Issues

None.

Check List

  • I have read the Contributing Guidelines and made commit messages that follow the guideline.
  • I have run pnpm format to ensure the code follows the style guide.
  • I have run pnpm test to check if all tests are passing.
  • I have run pnpm build to check if the website builds without errors.
  • I've covered new added functionality with unit tests if necessary.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sfj4nGFRyF6F4tLo3KmVjH

@mcollina
mcollina requested review from a team as code owners October 30, 2026 11:21
@vercel

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
nodejs-org Ready Ready Preview Oct 31, 2026 12:31pm UTC

Request Review

@codecov

codecov Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.21%. Comparing base (53d777b) to head (c5fa893).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ges/ui-components/src/Containers/MetaBar/index.tsx 60.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9250      +/-   ##
==========================================
+ Coverage   86.14%   86.21%   +0.06%     
==========================================
  Files          87       87              
  Lines        6164     6179      +15     
  Branches      369      370       +1     
==========================================
+ Hits         5310     5327      +17     
+ Misses        850      848       -2     
  Partials        4        4              

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

@ovflowd

ovflowd commented Oct 30, 2026

Copy link
Copy Markdown
Member

Oh boi... here we go.

@mcollina could you add screenshots of how it looks when there are ads?

  • do we already have plans to deal with the potential negative PR and influx of issues that will come with the fact we are serving ads? 🙈

@mcollina

Copy link
Copy Markdown
Member Author

These are the same ads mdn uses. I'll leave @rginn handle the public comms

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📦 Build Size Comparison

Summary

Metric Value
Old Total First Load JS 7.21 MB
New Total First Load JS 7.23 MB
Delta 19.08 KB (+0.26%)

Changes

🔄 Modified Routes (4)
Route Old First Load JS New First Load JS Delta
/[locale] 1.69 MB 1.70 MB 📈 4.77 KB (+0.28%)
/[locale]/[...path] 1.69 MB 1.70 MB 📈 4.77 KB (+0.28%)
/[locale]/blog/[...path] 1.69 MB 1.70 MB 📈 4.77 KB (+0.28%)
/[locale]/download/archive/[version] 1.69 MB 1.70 MB 📈 4.77 KB (+0.28%)

@ovflowd

This comment was marked as outdated.

@bmuenzenmeyer

This comment was marked as outdated.

@bmuenzenmeyer

bmuenzenmeyer commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

personally, I'm opposed to:

  • we also use banners in that region to signify major events like security releases. we are now intentionally drawing eyeballs up there for multiple, competing needs.
  • the top bar interrupting the nav to content eyeline and using extremely contrasting color.
  • the logo on the rightleft looks like too website headers loaded.

Important

Per content vs code, this is only my opinon.

@avivkeller

This comment was marked as outdated.

@bmuenzenmeyer

This comment was marked as outdated.

@bmuenzenmeyer

Copy link
Copy Markdown
Contributor

Learn and the API docs are separate applications and are not affected.

We've taken aims to make the three deployments look like a singular site, so I imagine we will have to extend this to doc-kit for at least learn, and perhaps API docs too.

doc-kit consumers from other projects may want this functionality anyway

@MattIPv4

Copy link
Copy Markdown
Member

👀 Two thoughts:

  1. The downloads page is what I'd consider to be quite an important/sensitive page. Are we worried about this rendering above the actual download information / pushing that information down?

  2. Are we concerned about the visuals of having a banner ad render below the nav while also having our own announcement banner render above the nav?

@ovflowd

ovflowd commented Oct 30, 2026

Copy link
Copy Markdown
Member

Learn and the API docs are separate applications and are not affected.

We've taken aims to make the three deployments look like a singular site, so I imagine we will have to extend this to doc-kit for at least learn, and perhaps API docs too.

doc-kit consumers from other projects may want this functionality anyway

Right, I'm talking to @avivkeller in person and we should make a @node-core/analytics package to put all analytics related things in there... Or maybe a component inside @node-core/ui-components like a Provider and a Component for Carbon and then the client/API keys etc get injected on each App.

@ovflowd ovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Adding a formal block for now due to the technical/design open questions we need to figure out first.

To be clear, we are not against the addition and we will comply to it, but we oughta get this aligned as anything else here first :)

Thank you for submitting this, tho, @mcollina

@mcollina

Copy link
Copy Markdown
Member Author

Can someone with better design skills take over on where to place those?
I literally throw this over the fence, and I'm not exactly the best person qualified for the job.

@bmuenzenmeyer @ovflowd wdyt about proposing something?

@mcollina

Copy link
Copy Markdown
Member Author

👀 Two thoughts:

  1. The downloads page is what I'd consider to be quite an important/sensitive page. Are we worried about this rendering above the actual download information / pushing that information down?

Yes, I think we can skip for this

  1. Are we concerned about the visuals of having a banner ad render below the nav while also having our own announcement banner render above the nav?

I'm not the best person to lead a long collaborative UI design discussion, and it would be great if somebody else take over.

@ovflowd

ovflowd commented Oct 30, 2026

Copy link
Copy Markdown
Member

Can someone with better design skills take over on where to place those? I literally throw this over the fence, and I'm not exactly the best person qualified for the job.

@bmuenzenmeyer @ovflowd wdyt about proposing something?

Absolutely, don't worry, we'll take care of it :)

@bmuenzenmeyer

Copy link
Copy Markdown
Contributor
  1. The downloads page is what I'd consider to be quite an important/sensitive page. Are we worried about this rendering above the actual download information / pushing that information down?

Yes, I think we can skip for this

addressed via 7b4b748

@bmuenzenmeyer

Copy link
Copy Markdown
Contributor

myself, @ovflowd and @avivkeller did an impromptu session

image

with potential security releases

image

as opposed to a potential future where we get this

image

cc @mcollina

@MattIPv4

MattIPv4 commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

with potential security releases
image

Very much a fan of this 👍

@bmuenzenmeyer

Copy link
Copy Markdown
Contributor

Pushed the new topbar strategy to all in-scope layouts for further review.
Aligned the border radii with other banners of this type
Updated the docs

Comment thread apps/site/components/MetaBar.tsx Resolved
@avivkeller

Copy link
Copy Markdown
Member

@avivkeller to be precise, you mean https://nodejs.org/docs/latest/api/ and any future doc-kit generated versions, correct?

I just post to clarify docs site as api docs

Correct, I mean the statically generated documentation we generate on every release.

Comment thread apps/site/hooks/useCarbonAd.ts Resolved
Comment thread apps/site/hooks/useCarbonAd.ts Resolved
Comment thread apps/site/hooks/useCarbonAd.ts Resolved

@ovflowd ovflowd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

SGTM with two final comments

@avivkeller avivkeller left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking to prevent accidental merges – @mcollina @rginn when should we actually merge this change and begin ads on the website?

Co-authored-by: Claudio Wunder <cwunder@gnome.org>
Co-authored-by: Brian Muenzenmeyer <brian.muenzenmeyer@gmail.com>
Co-authored-by: Matteo Collina <hello@matteocollina.com>

This branch was successfully deployed

2 active deployments
Preview — c5fa8936 Deployed Oct 31, 2026 by vercel[bot]
Storybook — c5fa8936 Deployed Oct 31, 2026 by avivkeller via Chromatic #2358
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants