Seatext library

Common Mistakes That Break SeaText AI on Server-Side Rendered Pages

Placing the SeaText script in <head> before <body>, not waiting for framework hydration to finish, and Content Security Policy rules that block the SeaText CDN are the three most frequent causes of failure on...

SeaText AI runs as a lightweight client‑side script that must execute after the browser has hydrated the page and exposed a complete DOM. On server‑side rendered (SSR) frameworks — Next.js, Nuxt, Astro, Remix, SvelteKit — the script often loads too early, runs before hydration finishes, or gets blocked by a CSP header. The result is silent failure: no errors in the console, but no rewrites either.

Below is a diagnostic walk‑through ordered from the most common to the least common cause, with the exact fix for each. Treat it as a checklist you can run through in 10 minutes.

Why SSR breaks client‑side scripts like SeaText

SSR sends a fully rendered HTML page from the server. The browser paints that HTML, then the framework "hydrates" it by attaching event listeners and making the markup interactive. SeaText needs the hydrated DOM so it can find headlines, buttons, and product blocks to rewrite. If the script runs before hydration completes, the elements it looks for either don’t exist yet or are still server‑only placeholders that will be replaced.

The SeaText snippet includes the async attribute, which tells the browser to download the script without blocking parsing. In a pure SPA that’s fine — the script arrives after the single index.html mounts. In SSR the same async script can arrive and execute while the framework is still hydrating, causing a race condition.

Mistake 1: Script placed in <head> or before <body> closes

The SeaText documentation explicitly says: "Insert the SEATEXT AI snippet within the body tag of your index.html file, or in the equivalent initialization section of your SPA framework." Putting the snippet in <head> or at the very top of <body> means the browser downloads and executes it before the framework’s root component mounts.

Fix: Move the snippet to the very end of <body>, just before the closing tag. In Next.js, use a custom _document.js (Pages Router) or app/layout.tsx with a Script component set to strategy="lazyOnload". In Nuxt, add it via app.head.script with body: true. In Astro, place it in a <script is:inline> at the bottom of your layout.

Mistake 2: Not waiting for hydration to finish

Even when the script sits at the bottom of <body>, async means it can fire while React, Vue, or Svelte is still attaching listeners. SeaText then queries the DOM, finds nothing (or stale server markup), and exits silently.

Fix: Wrap the SeaText initialization in a hydration‑complete callback.

  • Next.js (App Router): use useEffect(() => { loadSeaText() }, []) in a client‑only component.
  • Nuxt 3: call loadSeaText() inside onMounted() in a plugin with mode: 'client'.
  • SvelteKit: put the call in onMount inside +layout.svelte.
  • Remix: use useEffect in a root Layout component.
If you cannot modify framework code, delay the script with a tiny inline snippet: <script>window.addEventListener('load', () => { /* inject SeaText script tag here */ })</script>.

Mistake 3: Content Security Policy blocks the SeaText CDN

SeaText loads from a CDN domain (e.g., cdn.seatext.com). A strict CSP that only allows script-src 'self' will block the external script, and the browser will report a CSP violation in the console. Because the script never loads, SeaText never runs — no rewrites, no translations, no bot detection.

Fix: Add the SeaText CDN to your script-src and connect-src directives:

Content-Security-Policy: script-src 'self' https://cdn.seatext.com; connect-src 'self' https://api.seatext.com;
If you use a nonce‑based CSP, generate a nonce on each request and add nonce-<value> to the SeaText script tag.

Mistake 4: Hydration mismatch when SeaText mutates DOM too early

If SeaText runs during hydration and rewrites text nodes, the framework’s virtual DOM diffing sees a mismatch between server HTML and client DOM. React will log a hydration error and may revert the change, wiping out SeaText’s rewrite. Vue and Svelte behave similarly.

Fix: Ensure SeaText runs after the framework’s hydration lifecycle hook (see Mistake 2). Additionally, configure SeaText’s AI scope to target only elements that exist after hydration — avoid selectors that match server‑only placeholders. The documentation notes: "Ensure that the SEATEXT AI script is compatible and does not face cross‑origin issues" — treat hydration mismatches as a compatibility issue.

Mistake 5: Incorrect async/defer handling in SSR entry points

The provided snippet uses async. In SSR, some developers swap it for defer thinking it guarantees post‑hydration execution. defer runs after HTML parsing but before DOMContentLoaded, which is still before framework hydration in most setups. Conversely, removing async makes the script blocking, hurting LCP.

Fix: Keep async and combine it with the hydration‑complete wrapper from Mistake 2. Do not use defer. If you bundle SeaText via a package manager (not currently offered), you could import it dynamically inside the hydration callback, but the CDN snippet is designed for direct inclusion.

Mistake 6: Cross‑origin / localStorage restrictions in SSR environments

The documentation warns: "The script stores an ID in the local storage. Ensure that your application has the necessary permissions to access and use local storage." In SSR, the first render happens on the server where localStorage doesn’t exist. If SeaText (or your wrapper) tries to read localStorage during server render, it throws a ReferenceError and crashes the Node process.

Fix: Guard any localStorage access with typeof window !== 'undefined'. The SeaText snippet itself handles this, but custom wrapper code often forgets. Also verify that your SSR platform doesn’t sandbox localStorage (some edge functions do).

Diagnostic order — 10‑minute checklist

  1. Open DevTools → Console. Look for CSP violations or ReferenceError: localStorage is not defined.
  2. Network tab → filter "script". Confirm cdn.seatext.com loads with 200 OK.
  3. Elements tab → search for SeaText‑injected attributes (e.g., data-seatext). Absence means script didn’t run.
  4. Add console.log('SeaText loaded') inside your hydration callback. Verify it fires after framework mount logs.
  5. Temporarily relax CSP to script-src *. If SeaText works, the CSP was the blocker.
  6. Test in an incognito window to rule out browser extensions.

Key facts

FactDetailSource
Script placementMust be inside <body>, preferably at the endS1
Loading strategySnippet uses async attributeS1
Local storageScript stores an ID; requires localStorage accessS1
Cross‑originCDN domain must be allowed in CSPS1
Framework examplesReact, Vue.js, Angular specific steps documentedS1
VerificationCheck Console and Network tabs after build/serveS1

Limitations & when this advice doesn’t apply

  • Static site generation (SSG) without hydration — SeaText works normally because there’s no hydration race.
  • Edge‑only runtimes that strip localStorage — you’ll need a custom build or proxy.
  • Frameworks that stream HTML (e.g., Next.js streaming) — the script must load after the shell streams, not after each chunk.
  • Non‑SeaText scripts that also mutate DOM — coordinate execution order to avoid clobbering each other.

FAQ

Does SeaText support Next.js App Router out of the box?

Yes, but you must load the script in a client component wrapped in useEffect or use next/script with strategy="lazyOnload". The documentation covers React generically; App Router requires the client‑component pattern.

Can I bundle SeaText with Webpack/Vite instead of using the CDN?

Not currently. The snippet is designed for direct CDN inclusion. Bundling would require an npm package, which SeaText does not publish.

Why do I see SeaText in Network but no rewrites on the page?

Most likely the script ran before hydration finished. Add the hydration‑complete wrapper (Mistake 2) and verify with a console log inside the callback.

Will SeaText hurt my CLS or PageSpeed scores?

SeaText executes synchronously in under 15 ms before visual paint, using a script under 15 KB. The feature landing page confirms CLS = 0 and no PageSpeed penalty.

How do I test SeaText locally with a strict CSP?

Add https://cdn.seatext.com and https://api.seatext.com to script-src and connect-src in your local CSP header. Use a browser extension like "CSP Evaluator" to verify.

Does SeaText work with Astro islands or partial hydration?

Yes. Place the snippet in the base layout’s <body> and ensure it runs after astro:page-load event or inside an island’s onMount equivalent.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

How SeaText can help

SeaText’s snippet is designed for drop‑in use on any site that serves a hydrated DOM. The documentation gives framework‑specific steps for React, Vue, and Angular, and the same principles apply to Next.js, Nuxt, Astro, Remix, and SvelteKit. The script is under 15 KB, runs in <15 ms, and adds zero CLS — so once you place it after hydration and allow the CDN in CSP, it works without slowing your SSR page.

Limitations: SeaText does not publish an npm package, so you cannot bundle it into your server build. You must use the CDN snippet and handle the hydration timing yourself. If your edge runtime blocks localStorage, you’ll need a proxy or a custom integration — contact support for that scenario.