Skip to content
Oday Bakkour
Back to Knowledge Hub

Daily SEO Note — September 23, 2026: Critical next/og RCE Patched in Next.js 16.3.6

Oday Bakkour profile photo
Oday Bakkour
9 min read
Share
Daily SEO Note — September 23, 2026: Critical next/og RCE Patched in Next.js 16.3.6

1. SEO for Content Writers

Google published nothing new in the last 48 hours. The Search Status Dashboard reported no incidents across crawling, indexing, ranking and serving through 22 September 23:10 PDT, the Search Central blog's most recent post is dated 16 September and is an event announcement, and the documentation changelog has not logged an entry since 18 September, which the 21 September note already covered. The one editorial change that did land arrived without any announcement at all: Google's page experience guidance was revised on 22 September to point site owners at a set of advertising metrics, which makes ad load an explicitly measurable part of how Google frames page experience.

Google's page experience guidance now names ad density as something you can measure

The Understanding page experience in Google Search results document carries a last-updated stamp of 2026-09-22 UTC and now links out to CrUX ad metrics, described there as experimental Chrome metrics that "can help you understand your site's ad count, ad density, and ad weight metrics." Ad count is how many ads a typical user sees at once. Ad density is the balance between content and advertising visibility. Ad weight is split into CPU and network consumption.

This is a documentation change, not a ranking system change, and Google has not said these metrics feed ranking. What it does change is the vocabulary. Until now, an argument about how many ad slots sit between the introduction and the first substantive heading was a matter of taste between editorial and revenue. There is now a Google-referenced metric name attached to that argument, and it is reachable at origin and page level in the CrUX Vis dashboard for eligible sites.

For editors the practical read is that ad density is a content-layout decision as much as a monetisation one, because it is measured against content visibility rather than against page height. Long articles that load their top third with units are the ones that will read badly on this metric, and those are usually the evergreen explainers you least want to compromise.

Checkout is moving inside AI Mode, and eligible products may never get the click

Google's Universal Commerce Protocol documentation describes UCP-powered checkout as letting people buy directly inside AI Mode in Google Search or in Gemini, with the transaction completing on Google's surface rather than on the merchant's site. Merchant Center notifications telling retailers that eligible products are automatically included began circulating on 22 September. Rollout status is gradual, currently the United States, Canada and Australia, per the merchant onboarding guide.

The editorial consequence is narrow but sharp, and it only applies to commerce content. If the buying decision can be completed without a visit, then the parts of a product page that exist to close the sale are no longer doing the work they were written for. The parts that still matter are the ones an answer surface has to quote: specifications, compatibility, sizing, materials, what is in the box, and what the return terms actually are.

What to stop doing: writing product copy whose persuasive weight sits in on-page layout, urgency framing or a comparison that only makes sense next to your own cross-sell module. Those elements do not travel into an AI checkout surface. Plain, attributable product facts do.

Public Gemini Notebook pages are being used as a parasite-SEO surface (unconfirmed)

On 22 September, Search Engine Roundtable reported that public Gemini Notebook pages are being indexed at scale and used to rank spam, with the examples cited covering adult products, peptides and coupon content. The report originates from community observation on X, amplified by Gagan Ghotra and Glenn Gabe, and Google has not confirmed it or said whether the pages will be removed. Treat this as unconfirmed.

It is worth watching rather than acting on, for one reason: it is a textbook site reputation case running on a Google-owned domain. Google's spam policies already cover site reputation abuse and scaled content abuse, and the site reputation policy was itself updated on 28 August. If this gets cleaned up quietly, nothing changes for you. If it gets a policy response, the response will land on third-party-hosted content generally, which is the part that could reach your own guest, sponsored or partner sections.

No action today beyond awareness. Do not start pitching this as a tactic to clients; publishing on a surface that is about to be de-indexed is a way to lose a month.

Apply to your next brief

  • Add an ad-density line to the brief for any article over 1,500 words: name the maximum number of units above the first H2, and treat it as an editorial constraint, not a revenue one.
  • For commerce briefs, move specifications, compatibility and return terms out of tabs and accordions and into quotable body copy with a heading above them.
  • Stop writing product persuasion that depends on surrounding page furniture; assume the decisive paragraph will be read somewhere other than your page.
  • Leave the Gemini Notebook story out of client-facing recommendations until Google confirms a position; log it as a watch item only.
  • No changes to titles, meta descriptions or internal linking are warranted by anything that shipped in this window.

2. SEO for Developers

One change in this window is worth interrupting a sprint for. Next.js shipped an out-of-band security release on 22 September fixing a critical remote code execution path in the Node.js implementation of next/og ImageResponse, the component most sites use to generate Open Graph images for social and SERP previews. Everything else here is configuration work that can wait until the patch is out.

Next.js 16.3.6 patches a critical RCE in the Node.js next/og ImageResponse

Vercel published the security update on 22 September 2026. The Next.js advisory, GHSA-vcvr-r3jv-pc5j, is rated critical at CVSS 9.5 and affects npm next >= 16.2.0 < 16.3.6. The root cause is improper escaping in SVG output generated by Satori: attacker-controlled values passed into SVG content, attributes or styles can reach remote code execution through upstream dependencies. This is breaking in the operational sense. Ignore it and any route that renders an OG image from user-supplied text, a query parameter, a CMS field or a comment becomes an execution path.

Two details decide how urgent this is for you. First, only the Node.js runtime implementation is affected; applications using the Edge ImageResponse are not. Second, Next.js 15.x is not affected by the RCE at all, and 15.5.26 ships related hardening only, so a 15.x upgrade is prudent rather than urgent. If you cannot patch immediately, the documented interim mitigation is to stop passing attacker-controlled values into SVG content, attributes or styles rendered by the Node.js ImageResponse.

scripts/upgrade-next.sh
# Active LTS — contains the RCE fix
npm install [email protected]

# Maintenance LTS — hardening only, 15.x is NOT affected by the RCE
npm install [email protected]

# Confirm the resolved version, not just the range in package.json
npm ls next

The same Satori flaw reaches any stack that renders OG images, not just Next.js

The upstream advisory is GHSA-wx4j-mvgx-mqwp, published 22 September 2026, rated moderate at CVSS 5.3, affecting satori >= 0.0.27 < 0.33.5 and patched in 0.33.5. Satori failed to escape certain values before placing them into SVG output, so malicious input is interpreted as SVG markup rather than as text. The severity gap between the two advisories is the point: moderate upstream becomes critical once it is combined with the other dependencies in the Next.js image pipeline.

Satori sits underneath a lot of OG-image tooling beyond Next.js, including community integrations in the Astro, Nuxt and SvelteKit ecosystems. If your site generates social cards at build time or at the edge, check the resolved version rather than assuming your framework pinned it. A stale lockfile is the usual reason a transitive dependency survives a framework upgrade.

scripts/check-satori.sh
#!/usr/bin/env bash
set -euo pipefail

# Is satori in the tree at all, and at which resolved version?
npm ls satori --all || true

# Force the patched version through a transitive dependency
npm pkg set 'overrides.satori=^0.33.5'
npm install

# Verify nothing resolved below 0.33.5
npm ls satori --all | grep -E 'satori@'

Cloudflare Cache Rules now understand Vary, which changes which variant a crawler gets

Cloudflare shipped Vary support in Cache Rules on 22 September 2026, generally available on Free, Pro, Business and Enterprise plans, configurable through the dashboard, the Rulesets API and Terraform. Previously a Vary header in an origin response was largely a cache-bypass event; now each header can be handled with one of three actions. Normalize lowercases and sorts by quality value, with optional filtering to configured media types or languages. Passthrough matches on the raw header bytes. Bypass skips the cache entirely when that header is named.

This is non-breaking but it is not inert, because the default behaviour of a rule you write determines which cached variant an unauthenticated crawler receives. The failure mode to avoid is Passthrough on Accept-Language: Googlebot's header will rarely match a real user's byte-for-byte, so you fragment the cache and serve crawlers cold responses, which shows up as slower field data and, on large sites, thinner crawl coverage. Normalize with an explicit language allowlist is the setting that keeps localized variants correct without shattering the cache key.

scripts/cache-rule-vary.sh
# Create a Cache Rule that normalizes negotiation headers.
# Docs: https://blog.cloudflare.com/vary-support/
curl -X PUT \
  "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$RULESET_ID" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "rules": [{
      "expression": "(http.host eq \"example.com\")",
      "action": "set_cache_settings",
      "action_parameters": {
        "vary": {
          "default": { "action": "normalize" },
          "headers": {
            "accept-language": {
              "action": "normalize",
              "languages": ["en", "ar", "fr"]
            },
            "accept": {
              "action": "normalize",
              "media_types": ["text/html", "application/json"]
            }
          }
        }
      }
    }]
  }'

CrUX ad metrics give you a number for ad weight before it reaches field Core Web Vitals

The writer-facing half of this is in Section 1. The engineering half is that CrUX ad metrics now sit behind Google's page experience documentation as of its 2026-09-22 update, and they decompose a problem that INP and LCP only show you as an aggregate. Ad Weight: CPU and Ad Weight: Network attribute main-thread and bandwidth cost specifically to ad frames, which is the attribution step that usually takes a morning of manual tracing.

One constraint worth knowing before you plan work around this: the metrics are described as experimental and are surfaced for eligible sites in the CrUX Vis dashboard at origin and page level. They are not documented as fields in the CrUX API response, so do not write a monitor against them yet. Keep your automated regression checks on the standard CrUX record and use CrUX Vis for the ad breakdown manually.

scripts/crux-check.sh
#!/usr/bin/env bash
# Standard CrUX record — safe to automate.
# Ad metrics are experimental and dashboard-only (cruxvis.withgoogle.com),
# so do NOT add them to this monitor yet.
curl -s -X POST \
  "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$CRUX_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "origin": "https://example.com",
    "formFactor": "PHONE",
    "metrics": [
      "largest_contentful_paint",
      "interaction_to_next_paint",
      "cumulative_layout_shift"
    ]
  }' | jq '.record.metrics | to_entries[] | {(.key): .value.percentiles.p75}'

Ship today

  1. Upgrade to [email protected] on every Next.js 16.2.x–16.3.5 application and redeploy. This is the only item here that should not wait.
  2. Audit the resolved satori version across all repositories that generate OG images, including non-Next.js stacks, and force >= 0.33.5 via overrides where a transitive dependency lags.
  3. Grep your OG image routes for any value that originates from a query parameter, CMS field or user submission, and escape or allowlist it regardless of the patch level you land on.
  4. Review any Cloudflare Cache Rule you add for Vary: use Normalize with an explicit language allowlist rather than Passthrough on Accept-Language.
  5. Leave CrUX ad metrics out of automated monitoring; check them manually in CrUX Vis and log a baseline for your heaviest templates.
Add Oday Bakkour as a preferred source on Google

Comments

Share your thoughts and join the conversation

Leave a Comment

Loading comments...
RELATED
Daily SEO Note: Next.js Patches Critical next/og RCE | Oday Bakkour