NextJS Warning: Extra attributes from the server – Fix 2026
Developer Guide

NextJS Warning: Extra attributes from the server – Fix 2026

Seeing the warning "Extra attributes from the server: data‑new‑gr‑c‑s‑check‑loaded"? Learn why it appears and how to eliminate it in your Next.js + Supabase app.

2026-06-03
7 min read
NextJS Warning: Extra attributes from the server – Fix 2026

TL;DR#

If you're seeing Warning: Extra attributes from the server: data-new-gr-c-s-check-loaded="...", the cause is usually a third‑party script or browser extension that mutates the server‑rendered markup. Fix it by loading external scripts with next/script or by sanitising the markup in a custom _document.

If that doesn't work, scroll to verify the fix — there are two common variants this guide also covers.

What you'll see#

text
Warning: Extra attributes from the server: data-new-gr-c-s-check-loaded="true"
    at ./node_modules/next/dist/compiled/react-dom/cjs/react-dom-server.browser.development.js:1234:15

It happens when the page is rendered on the server, sent to the browser, and then React tries to hydrate the markup on the client. The server‑side HTML contains a data-new-gr-c-s-check-loaded attribute that the client‑side React tree never creates, so React logs the warning and continues. The behavior is reproducible on local development (npm run dev) as well as on Vercel preview deployments, but only when the offending script is present. In my own Next.js + Supabase project the warning appeared after adding a third‑party analytics snippet directly inside a component.

Root cause#

The warning is a direct result of a hydration mismatch. During the initial render Next.js streams HTML generated by React on the server. After the browser receives that HTML, React mounts the same component tree on the client and expects the DOM to match exactly. If any attribute appears in the server markup that React never renders, it flags the difference as “extra attributes from the server”.

Two common culprits inject the data-new-gr-c-s-check-loaded attribute:

  1. Browser extensions – Grammarly, for example, adds a data-gr-ext-installed attribute to every <html> element. When the extension runs on a page that was server‑rendered, the attribute is present only in the client DOM, but because the extension runs after the initial HTML is parsed, the server markup does not contain it, leading to the warning.

  2. Third‑party scripts added via plain <script> tags – Many analytics or widget providers ask you to paste a <script src="…"> snippet directly into your JSX. If that script writes data-* attributes onto the root element (or any element) during its load phase, the server‑side HTML will miss those attributes, and React will warn.

Both scenarios break the invariant that server and client markup must be identical before hydration. The warning itself is harmless – the page still works – but it signals a hidden source of nondeterminism that can cause subtle bugs, especially when you rely on server‑only data (e.g., Supabase session cookies) that might be overwritten.

The relevant code path inside Next.js is the hydration check in react-dom-server.browser.development.js. When the client reconciles, it calls assertValidAttributes which compares the attribute list of each element. Any attribute present only on the server side triggers the warning you see.

js
// node_modules/react-dom/cjs/react-dom-server.browser.development.js
function assertValidAttributes(domElement, props) {
  // Simplified excerpt
  for (const name in domElement.getAttributeNames()) {
    if (!props.hasOwnProperty(name) && !isValidExtraAttribute(name)) {
      console.warn(
        `Warning: Extra attributes from the server: ${name}="${domElement.getAttribute(name)}"`
      );
    }
  }
}

The fix#

The fix has two parts: (1) stop third‑party scripts from mutating the markup, and (2) optionally strip any stray attributes that extensions might add during the initial render.

1. Load external scripts with next/script#

next/script defers script execution until after React has hydrated, preventing the script from touching the server‑generated HTML.

tsx
// pages/_app.tsx
import type { AppProps } from 'next/app';
import Script from 'next/script';

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      {/* Example: Google Analytics */}
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"
        strategy="afterInteractive"
      />
      <Script id="ga-init" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'G-XXXXXXX');
        `}
      </Script>

      <Component {...pageProps} />
    </>
  );
}

That single change ensures the analytics script runs after the React tree is already attached, so any data-* attributes it adds will appear only on the client side after hydration, and React will not compare them.

2. Sanitize the markup in a custom _document#

If you cannot control a third‑party script (or you need to support users with extensions that inject attributes), you can strip those attributes during the server render. Create a custom _document that removes any data-gr-* attributes from the <html> element before sending HTML to the browser.

tsx
// pages/_document.tsx
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document';

export default class MyDocument extends Document {
  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);
    // Clone the HTML to manipulate it safely
    const html = initialProps.html.replace(
      / data-gr-[a-z-]+="[^"]*"/g,
      ''
    );
    return { ...initialProps, html };
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Before / After diff#

diff
- <Html data-gr-ext-installed="true" data-new-gr-c-s-check-loaded="true">
+ <Html>

The regex removes any attribute that starts with data-gr- or data-new-gr-c-s-check-loaded. This approach is safe because those attributes are never used by your application logic; they are purely injected by external tools.

3. Guard DOM manipulation in useEffect#

If you have custom useEffect code that writes attributes, wrap it in a check that runs only after the component is mounted.

tsx
// components/AnalyticsProvider.tsx
import { useEffect } from 'react';

export default function AnalyticsProvider() {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      const script = document.createElement('script');
      script.src = 'https://example.com/widget.js';
      script.async = true;
      document.body.appendChild(script);
    }
  }, []);

  return null;
}

By ensuring the script runs after the first paint, you avoid contaminating the server markup.

Step by step#

  1. Identify the offending script – Look at the warning’s attribute name. If it contains gr-, suspect Grammarly or a similar extension. If it’s a custom analytics name, locate the <script> tag you added.
  2. Replace raw <script> tags with the next/script component as shown above. Use strategy="afterInteractive" for most cases.
  3. Add a custom _document if you need a blanket removal of data-gr-* attributes. Paste the code exactly; the regex will strip any matching attribute.
  4. Run the dev server (npm run dev). The warning should disappear.
  5. Deploy and verify on Vercel preview; the warning should stay gone because the server‑side HTML no longer contains the extra attributes.

Verify the fix#

Run the development server:

bash
npm run dev

Open http://localhost:3000 in a clean browser (disable extensions). You should see no warning in the terminal or browser console.

text
> next dev
ready - started server on http://localhost:3000

If the warning still appears, open the page source (Ctrl+U) and search for data-new-gr-c-s-check-loaded. If it is absent, the server markup is clean. Then open the DevTools Elements panel and look for the same attribute; if it appears only after the page loads, it means a client‑side script is still injecting it, and you need to adjust the script’s loading strategy.

Variant A — Extension‑only injection#

If you cannot control the user’s browser extensions, the _document sanitisation is the reliable fix. The regex will strip the attribute regardless of which extension added it.

Variant B — Inline script that writes attributes early#

If you have an inline script that runs before React hydrates (e.g., a legacy analytics snippet placed in pages/index.tsx), move it to next/script with strategy="afterInteractive" or wrap it in a useEffect as shown earlier. This prevents the attribute from existing in the server HTML.

Why this happens (and how to avoid it next time)#

React’s hydration algorithm assumes a deterministic render: given the same props and state, the server and client must produce identical DOM trees. Anything that mutates the DOM between the server response and the client render breaks that assumption. The safest pattern is:

  • Never embed raw <script> tags that execute synchronously. Use next/script with an explicit loading strategy.
  • Avoid DOM manipulation in the top‑level component body; always place it inside useEffect or useLayoutEffect.
  • Test with extensions disabled or use a headless browser (e.g., Playwright) to catch unexpected attribute injection before shipping.
  • Add a lint rule (via ESLint) that flags <script> JSX elements without next/script. Example rule configuration can be found in the official Next.js ESLint plugin.

By keeping the server‑generated markup pure and deferring any side‑effects until after hydration, you eliminate the “Extra attributes from the server” warning and keep your Next.js + Supabase app stable.

Frequently Asked Questions

|

Have more questions? Contact us

One email a month — no fluff

RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.