React / Next.js
Inclusify installs in a Next.js app as one next/script element in the root layout, app/layout.tsx in the App Router or pages/_app.js in the Pages Router. The root layout is the placement that matters: Next.js documents a script there as loading for every route and loading only once even as the user navigates between pages. There is no plan, permission, package or provider to add: it is one import, one element, and one deploy. The one Next.js-specific thing to know before you go looking for a bug is that the default strategy injects the tag client-side, so it will not be in View Source on a correct install.
Register the domain first
Add your domain in the Inclusify panel before you paste the script tag. Bundles are built per registered domain, and an unregistered one fails silently: the script URL still returns HTTP 200, but what it serves is a single console.log saying the domain is not registered. There is no error to find and no widget appears.
Next.js (App Router)
Use the Next.js Script component in your root layout, so it mounts once and survives client-side navigation:
import Script from 'next/script'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://app.inclusifyapp.com/w/YOUR-DOMAIN.com.js"
strategy="afterInteractive"
/>
</body>
</html>
)
}afterInteractive is the default and the right choice here. Next.js describes it as injecting the tag into the HTML client-side and loading after some hydration occurs, so the widget loads promptly without sitting on the critical path. It also means the tag is not in the server-rendered HTML: check the loaded DOM or the network tab, not View Source. The alternative, beforeInteractive, is documented for critical scripts such as bot detectors and consent managers and is the wrong choice for an accessibility widget.
Next.js (Pages Router)
Add to your custom _app.js:
import Script from 'next/script'
export default function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<Script
src="https://app.inclusifyapp.com/w/YOUR-DOMAIN.com.js"
strategy="afterInteractive"
/>
</>
)
}React (Vite, CRA)
Add the script to your index.html:
<script src="https://app.inclusifyapp.com/w/YOUR-DOMAIN.com.js" async></script>Add before the closing </body> tag.
Notes
- • Works with all React frameworks: Next.js, Remix, Gatsby, Vite and Create React App
- • Safe under SSR. The tag is inert markup on the server and the widget only executes in the browser, so it cannot cause a hydration mismatch
- • No React component and no npm package. The widget initializes itself from the script tag and exposes no React API, so there is nothing to import, wrap in a provider or clean up in an effect
- • Extra attributes you pass to
next/script, a CSPnoncefor instance, are forwarded to the script element it renders - • Preview deployments will not run it. A Vercel commit preview URL carries a nine-character random hash, so its hostname is new on every commit, and the bundle only runs on hostnames registered to your Inclusify website.
localhostis exempt, so local development works
What needs attention on React / Next.js
The root layout is the placement that loads it once
Next.js documents a Script in the root layout as loading and executing when any route in the application is accessed, and says it will ensure the script only loads once even if a user navigates between multiple pages. A Script inside a page component gets torn down and re-created as the user moves around; the root layout is what makes one instance for the session.
afterInteractive is the default, and it is client-injected
Next.js lists four strategies and marks afterInteractive as the default: scripts using it are injected into the HTML client-side and load after some hydration occurs. beforeInteractive is injected into the initial HTML from the server, must be placed in a root layout, and is documented as being for critical scripts such as bot detectors and consent managers, which this is not. lazyOnload waits for browser idle time and also works.
Preview deployments get a hostname you cannot register in advance
A Vercel commit preview URL is <project-name>-<unique-hash>-<scope-slug>.vercel.app, where the hash is nine randomly generated characters, so every commit produces a new hostname. The bundle checks window.location.hostname against the domains registered to your Inclusify website before it renders anything, and logs "Inclusify: This widget is not licensed for" and that hostname when it does not match. localhost and 127.0.0.1 are the only exemptions. Branch URLs are stable, so a long-lived branch can be registered; per-commit previews cannot, and local development works because localhost is exempt.
There is no package to install and no component to import
The widget initialises itself from the script tag and exposes no React API, so nothing needs to be imported, wrapped in a provider or cleaned up in an effect. Any extra attributes you pass to next/script, a nonce for instance, are forwarded to the script element it renders.
Verify the installation
- Run a production build rather than the dev server. next/script behaves differently under development's Fast Refresh, and React StrictMode double-invokes effects in development only.
- Do not use View Source. Next.js injects an afterInteractive script into the HTML client-side, so it is absent from the server-rendered markup on a correct install. Look in the Elements panel, or filter the Network tab for "w/" and confirm the request to app.inclusifyapp.com returns 200.
- Navigate between routes client-side and confirm the widget stays mounted. It attaches to the document rather than to the React tree, and Next.js loads a root-layout script only once across navigations.
- Test on localhost or on your production hostname, not on a preview deployment. The bundle checks window.location.hostname against the domains registered to your Inclusify website before it renders anything, and logs "Inclusify: This widget is not licensed for" and that hostname when it does not match. localhost and 127.0.0.1 are the only exemptions.
- If you set a Content-Security-Policy header, check the console on a production build: a missing script-src entry shows up there and nowhere else.
Widget bundles are served from a CDN and cached for a few hours. If you changed widget settings in the panel and the site still shows the old configuration, wait for the cache to expire or load the script URL directly with a cache-busting query string (`?v=2`) to confirm the new version is being served.
Troubleshooting React / Next.js installs
- The script loads twice in development.
- React StrictMode double-invokes effects in development only. Check a production build before treating it as a bug.
- The browser blocks the request with a CSP error.
- Add https://app.inclusifyapp.com to script-src and connect-src in the Content-Security-Policy header you set in next.config.js or middleware. If you use a nonce, pass it to next/script, which forwards additional attributes to the rendered script element.
- The widget disappears after a client-side route change.
- The script was mounted inside a page component that unmounts on navigation. Move it to the root layout in the App Router, or to _app in the Pages Router, where Next.js loads it once for every route.
- A preview deployment logs that the widget is not licensed.
- Vercel gives each commit preview a fresh hostname, and the bundle only runs on registered hostnames plus localhost. Test locally or on the production hostname, or register the stable branch URL if you use one.
- Nothing loads in a static export.
- A static export comes from output: "export" in next.config.js plus a normal next build; the standalone next export command was removed in Next.js 14. Do not judge it by the HTML in out/, because an afterInteractive tag is injected client-side and will not be in that file even when the install is correct. Load an exported page in a browser and check the request to app.inclusifyapp.com instead.
React / Next.js questions
- Which next/script strategy should I use?
- afterInteractive, which is also the default. The widget does not need to run before hydration, and beforeInteractive is documented as being for critical scripts such as bot detectors and consent managers. lazyOnload also works if you would rather it waited for browser idle time.
- Do I need a React component or a wrapper package?
- No. The widget initialises itself from the script tag and does not expose or require a React API.
- Where exactly should the Script go?
- In the root layout, app/layout.tsx in the App Router or pages/_app.js in the Pages Router. Next.js documents a root-layout script as loading for every route and loading only once across client-side navigations; a script in a page component is re-created as the user moves between routes.
- Does it work with Remix, Gatsby, Vite or Create React App?
- Yes. Any of them can render a plain script tag in the document shell: index.html for Vite and Create React App, the root route document for Remix.
- Is it safe with server-side rendering?
- Yes. The tag is inert markup on the server and the widget only executes in the browser, so it does not affect the SSR pass or cause a hydration mismatch.
- Will it run on a Vercel preview deployment?
- Not on a per-commit preview. Those hostnames contain a nine-character random hash and change with every commit, and the bundle only runs on hostnames registered to your Inclusify website, plus localhost. Register a stable branch URL if you need previews, or test locally.
React / Next.js documentation
Every React / Next.js-specific requirement, menu path and limit on this page comes from these pages. Menus move, so check them against your own admin if something does not match.
Related
- Script installation generates the embed code for your domain.
- Customization covers the position, colour and size of the widget button.