1"use strict";(self.webpackChunkgatsby_starter_default=self.webpackChunkgatsby_starter_default||[]).push([[3124],{61572:function(e,n,a){a.r(n),a.d(n,{Head:function(){return h},default:function(){return g}});var t=a(28453),s=a(96540);function o(e){const n=Object.assign({h1:"h1",a:"a",span:"span",p:"p",h2:"h2",h3:"h3",ul:"ul",li:"li",strong:"strong",em:"em",table:"table",thead:"thead",tr:"tr",th:"th",tbody:"tbody",td:"td",ol:"ol"},(0,t.R)(),e.components);return s.createElement(s.Fragment,null,s.createElement(n.h1,{id:"debugging-hydration-mismatches-in-rails",style:{position:"relative"}},s.createElement(n.a,{href:"#debugging-hydration-mismatches-in-rails","aria-label":"debugging hydration mismatches in rails permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Debugging Hydration Mismatches in Rails"),"\n",s.createElement(n.p,null,'A hydration mismatch happens when the HTML your Rails server rendered for a React component does not match what React renders on the client during hydration. React 18+ recovers automatically by throwing away the server HTML and re-rendering on the client â which means the page still "works," but you pay for the server render twice, the UI can visibly flicker, and the underlying bug stays hidden unless you are watching for it.'),"\n",s.createElement(n.p,null,"Rails apps hit a set of systematic causes that JavaScript-only frameworks rarely see, because the server render happens in a Rails request context (time zones, ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">current_user</code>'}}),", I18n, CSRF, asset hosts) while the client render does not. This guide catalogs those causes, the fix patterns, and how to observe hydration errors with React on Rails' root error callbacks."),"\n",s.createElement(n.h2,{id:"observing-hydration-errors",style:{position:"relative"}},s.createElement(n.a,{href:"#observing-hydration-errors","aria-label":"observing hydration errors permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Observing hydration errors"),"\n",s.createElement(n.h3,{id:"development-default",style:{position:"relative"}},s.createElement(n.a,{href:"#development-default","aria-label":"development default permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Development default"),"\n",s.createElement(n.p,null,"When ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Rails.env.development?</code>'}}),", React on Rails automatically logs every recoverable hydration error with the component name, the DOM id, the component stack (when React provides one), and a link to this guide:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">[ReactOnRails] Recoverable hydration error in component "MyComponent" (dom id: "MyComponent-react-component-0"). ...\nComponent stack:\n at MyComponent</code></pre></div>'}}),"\n",s.createElement(n.p,null,"React's default error reporting is preserved: the error itself is still reported once via ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reportError</code>'}})," (falling back to ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">console.error</code>'}}),"), so dev overlays and other window-",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:"<code class=\"language-text\">'error'</code>"}}),"-based tooling keep working. The branded line is supplemental context. On React on Rails Pro RSC hydration paths, Pro's internal handler already reports the error, so only the supplemental line is added â each error is reported exactly once either way."),"\n",s.createElement(n.p,null,"No setup is required. The default logger runs in addition to any callback y
1ou register."),"\n",s.createElement(n.h3,{id:"registering-root-error-callbacks-react-19",style:{position:"relative"}},s.createElement(n.a,{href:"#registering-root-error-callbacks-react-19","aria-label":"registering root error callbacks react 19 permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Registering root error callbacks (React 19)"),"\n",s.createElement(n.p,null,"React's root APIs accept error callbacks, and React on Rails exposes them globally through ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ReactOnRails.setOptions</code>'}}),". They apply to every React root that React on Rails creates (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">hydrateRoot</code>'}})," and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">createRoot</code>'}}),"):"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token comment">// In your client bundle entry, before your components render</span>\n<span class="token comment">// (typically next to your ReactOnRails.register call).</span>\n<span class="token keyword">import</span> ReactOnRails <span class="token keyword">from</span> <span class="token string">\'react-on-rails/client\'</span><span class="token punctuation">;</span>\n\nReactOnRails<span class="token punctuation">.</span><span class="token function">setOptions</span><span class="token punctuation">(</span><span class="token punctuation">{</span>\n <span class="token literal-property property">rootErrorHandlers</span><span class="token operator">:</span> <span class="token punctuation">{</span>\n <span class="token comment">// React recovered from an error (most commonly a hydration mismatch). React 18+.</span>\n <span class="token function-variable function">onRecoverableError</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> errorInfo<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n <span class="token keyword">const</span> rootContext <span class="token operator">=</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">componentName</span><span class="token operator">:</span> context<span class="token punctuation">.</span>componentName <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token literal-property property">domNodeId</span><span class="token operator">:</span> context<span class="token punctuation">.</span>domNodeId <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">;</span>\n myErrorReporter<span class="token punctuation">.</span><span class="token function">
1report</span><span class="token punctuation">(</span>error<span class="token punctuation">,</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">level</span><span class="token operator">:</span> <span class="token string">\'warning\'</span><span class="token punctuation">,</span>\n <span class="token operator">...</span>rootContext<span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token comment">// An error boundary caught an error. React 19+.</span>\n <span class="token function-variable function">onCaughtError</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> errorInfo<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n <span class="token keyword">const</span> rootContext <span class="token operator">=</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">componentName</span><span class="token operator">:</span> context<span class="token punctuation">.</span>componentName <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token literal-property property">domNodeId</span><span class="token operator">:</span> context<span class="token punctuation">.</span>domNodeId <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">;</span>\n myErrorReporter<span class="token punctuation">.</span><span class="token function">
1report</span><span class="token punctuation">(</span>error<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">level</span><span class="token operator">:</span> <span class="token string">\'error\'</span><span class="token punctuation">,</span> <span class="token literal-property property">handled</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token operator">...</span>rootContext <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token comment">// An error was NOT caught by any error boundary. React 19+.</span>\n <span class="token function-variable function">onUncaughtError</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> errorInfo<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n <span class="token keyword">const</span> rootContext <span class="token operator">=</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">componentName</span><span class="token operator">:</span> context<span class="token punctuation">.</span>componentName <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token literal-property property">domNodeId</span><span class="token operator">:</span> context<span class="token punctuation">.</span>domNodeId <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">;</span>\n myErrorReporter<span class="token punctuation">.</span><span class="token function">
1report</span><span class="token punctuation">(</span>error<span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token literal-property property">level</span><span class="token operator">:</span> <span class="token string">\'fatal\'</span><span class="token punctuation">,</span> <span class="token literal-property property">handled</span><span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token operator">...</span>rootContext <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,"Each callback receives React's original ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">(error, errorInfo)</code>'}})," arguments plus a ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">context</code>'}})," object that may include the React on Rails ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">componentName</code>'}})," and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">domNodeId</code>'}})," of the affected root. Treat both fields as optional because lower-level render paths or custom mount nodes may not supply one."),"\n",s.createElement(n.p,null,"Notes:"),"\n",s.createElement(n.ul,null,"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"Register before rendering.")," Each root captures the callbacks registered when it is created; register them in the same pack file where you call ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ReactOnRails.register</code>'}}),"."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"Partial updates merge per key.")," A later ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">setOptions({ rootErrorHandlers: { onCaughtError } })</code>'}})," keeps a previously registered ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onRecoverableError</code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onUncaughtError</code>'}}),". Pass an explicit ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">undefined</code>'}})," for a key to clear just that callback; ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ReactOnRails.resetOptions()</code>'}})," clears all of them."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"React version support:")," ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onRecoverableError</code>'}})," requires React 18+; ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onCaughtError</code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onUncaughtError</code>'}})," require React 19. On unsupported React versions, React on Rails still stores the registered handlers so they start working after a React upgrade, but the current runtime cannot invoke them and logs a one-time ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">console.warn</code>'}}),"."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"React on Rails Pro:")," on RSC/streaming hydration paths, Pro installs an internal ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onRecoverableError</code>'}})," for its own bookkeeping. Your callback is chained after it â both always run, and Pro's internal control-flow signals (such as the ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">RSCRoute</code>'}})," ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">ssr: false</code>'}})," bailout) are filtered out of both so they never reach your error reporter. The Pro RSC hydration chain already calls ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reportError</code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">console.error</code>'}})," before your callback, so do not call ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reportError(error)</code>'}})," again in that callback on RSC hydrate roots."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"Development default scope:")," the branded ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">[ReactOnRails]</code>'}})," guide-link log is only for recoverable hydration errors. ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onCaughtError</code>'}})," and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onUncaughtError</code>'}})," still receive the enriched context, but React on Rails does not add a supplemental guide log for those non-hydration paths."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"Default reporting for ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onRecoverableError</code>'}}),":")," registering this callback replaces React's own default ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reportError</code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">console.error</code>'}})," for that root (standard React semantics). React on Rails re-emits recoverable hydration errors in development so dev overlays keep working on hydrated roots; client-only ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">createRoot</code>'}})," roots use the standard React callback semantics, so your callback is the reporter. If you rely on separate ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">window.onerror</code>'}}),"-based monitoring, call ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reportError(error)</code>'}})," inside your callback or configure your monitoring SDK to capture ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">window.reportError</code>'}}),". Exception: on React on Rails Pro RSC hydrate roots, Pro already calls ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">reportError</code>'}})," before your callback, so calling it again there will double-report."),"\n",s.createElement(n.li,null,s.createElement(n.strong,null,"Per-component overrides")," are not currently supported; the global registration above is the blessed route."),"\n"),"\n",s.createElement(n.h3,{id:"sentry-example-and-avoiding-double-reporting",style:{position:"relative"}}
1,s.createElement(n.a,{href:"#sentry-example-and-avoiding-double-reporting","aria-label":"sentry example and avoiding double reporting permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Sentry example (and avoiding double reporting)"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> Sentry <span class="token keyword">from</span> <span class="token string">\'@sentry/react\'</span><span class="token punctuation">;</span>\n<span class="token keyword">import</span> ReactOnRails <span class="token keyword">from</span> <span class="token string">\'react-on-rails/client\'</span><span class="token punctuation">;</span>\n\nReactOnRails<span class="token punctuation">.</span><span class="token function">setOptions</span><span class="token punctuation">(</span><span class="token punctuation">{</span>\n <span class="token literal-property property">rootErrorHandlers</span><span class="token operator">:</span> <span class="token punctuation">{</span>\n <span class="token function-variable function">onRecoverableError</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> errorInfo<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n Sentry<span class="token punctuation">.</span><span class="token function">captureException</span><span class="token punctuation">(</span>error<span class="token punctuation">,</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">level</span><span class="token operator">:</span> <span class="token string">\'warning\'</span><span class="token punctuation">,</span>\n <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">ror_component</span><span class="token operator">:</span> context<span class="token punctuation">.</span>componentName <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token literal-property property">ror_dom_id</span><span class="token operator">:</span> context<span class="token punctuation">.</span>domNodeId <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token function-variable function">onUncaughtError</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> errorInfo<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n Sentry<span class="token punctuation">.</span><span class="token function">captureException</span><span class="token punctuation">(</span>error<span class="token punctuation">,</span> <span class="token punctuation">{</span>
1\n <span class="token literal-property property">tags</span><span class="token operator">:</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">ror_component</span><span class="token operator">:</span> context<span class="token punctuation">.</span>componentName <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token literal-property property">ror_dom_id</span><span class="token operator">:</span> context<span class="token punctuation">.</span>domNodeId <span class="token operator">??</span> <span class="token string">\'unknown\'</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token punctuation">}</span><span class="token punctuation">,</span>\n<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Precedence:")," an error is routed to exactly one of React's callbacks. If an error boundary catches it, it reaches ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onCaughtError</code>'}})," (not ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onUncaughtError</code>'}}),"); otherwise it reaches ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onUncaughtError</code>'}}),". But error boundaries themselves (like ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Sentry.ErrorBoundary</code>'}})," or a ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">componentDidCatch</code>'}})," that reports) run ",s.createElement(n.strong,null,"in addition to")," ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onCaughtError</code>'}})," â so if you report from both your boundary and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onCaughtError</code>'}}),", the same error is reported twice. Pick one layer: either report from boundaries and leave ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onCaughtError</code>'}})," for logging/metrics, or report from ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onCaughtError</code>'}})," and keep boundaries presentational. Similarly, ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">window.onerror</code>'}}),"-based reporters may also see uncaught errors that React rethrows; deduplicate by error identity if you wire both."),"\n",s.createElement(n.h2,{id:"rail
1s-specific-causes-and-fixes",style:{position:"relative"}},s.createElement(n.a,{href:"#rails-specific-causes-and-fixes","aria-label":"rails specific causes and fixes permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Rails-specific causes and fixes"),"\n",s.createElement(n.h3,{id:"1-time-dates-and-time-zones-timecurrent-l-relative-times",style:{position:"relative"}},s.createElement(n.a,{href:"#1-time-dates-and-time-zones-timecurrent-l-relative-times","aria-label":"1 time dates and time zones timecurrent l relative times permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"1. Time, dates, and time zones (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Time.current</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">l(...)</code>'}}),", relative times)"),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Symptom:"),' timestamps, "x minutes ago" labels, or date-dependent UI differ between the server HTML and the client render.'),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Why:")," the server renders with the Rails process time (and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Time.zone</code>'}}),'), while the client renders with the browser clock and locale â even a one-second delta changes "rendered at" strings. Passing ',s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Time.current.to_s</code>'}})," as a prop is fine; ",s.createElement(n.em,null,"formatting the current time independently on both sides")," is not."),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="erb"><pre class="language-erb"><code class="language-erb"><span class="token erb language-erb"><span class="token delimiter punctuation"><%</span><span class="token ruby language-ruby"><span class="token comment"># BAD: the view bakes a server-formatted "now" next to a component that renders its own clock </span></span><span class="token delimiter punctuation">%></span></span>\n<span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span><span class="token ruby language-ruby"> react_component<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"OrderSummary"</span></span><span class="token punctuation">,</span> <span class="token symbol">props</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">renderedAt</span><span class="token operator">:</span> <span class="token builtin">Time</span><span class="token punctuation">.</span>current<span class="token punctuation">.</span>strftime<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"%H:%M:%S"</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token symbol">prerender</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span> </span><span class="token delimiter punctuation">%></span></span></code></pre></div>'}}),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Fixes:")),"\n",s.createElement(n.ul,null,"\n",s.createElement(n.li,null,"\n",s.createElement(n.p,null,"Pass a stable value (epoch milliseconds or ISO8601 string) as a prop and format it the same way on both sides:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="erb"><pre class="language-erb"><code class="language-erb"><span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span><span class="token ruby language-ruby"> react_component<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"OrderSummary"</span></span><span class="token punctuation">,</span> <span class="token symbol">props</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">createdAtMs</span><span class="token operator">:</span> <span class="token variable">
1@order</span><span class="token punctuation">.</span>created_at<span class="token punctuation">.</span>to_i <span class="token operator">*</span> <span class="token number">1000</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token symbol">prerender</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span> </span><span class="token delimiter punctuation">%></span></span></code></pre></div>'}}),"\n"),"\n",s.createElement(n.li,null,"\n",s.createElement(n.p,null,"For values that ",s.createElement(n.em,null,"must"),' differ (live clocks, relative "ago" labels), render them client-side only â initialize state to the server-safe value and update in ',s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useEffect</code>'}}),":"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">TimeAgo</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">{</span> createdAtMs <span class="token punctuation">}</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span>\n <span class="token keyword">const</span> <span class="token punctuation">[</span>label<span class="token punctuation">,</span> setLabel<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">useState</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// server renders nothing dynamic</span>\n <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setLabel</span><span class="token punctuation">(</span><span class="token function">formatTimeAgo</span><span class="token punctuation">(</span>createdAtMs<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>createdAtMs<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>\n <span class="token keyword">return</span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">suppressHydrationWarning</span><span class="token punctuation">></span></span><span class="token punctuation">{</span>label <span class="token operator">??</span> <span class="token string">\'â¦\'</span><span class="token punctuation">}</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span><span class="token punctuation">;</span>\n<span class="token punctuation">}</span></code></pre></div>'}}),"\n"),"\n",s.createElement(n.li,null,"\n",s.createElement(n.p,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">suppressHydrationWarning</code>'}})," on the single text node that legitimately differs is acceptable for things like timestamps. It only suppresses one element's text/attribute warning â do not wrap whole trees in it to silence a real bug."),"\n"),"\n"),"\n",s.createElement(n.h3,{id:"2-current_user-conditional-erb-around-components",style:{position:"relative"}},s.createElement(n.a,{href:"#2-current_user-conditional-erb-around-components","aria-label":"2 current_user conditional erb around components permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"2. ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">current_user</code>'}}),"-conditional ERB around components"),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Symptom:")," components hydrate fine logged out but mismatch (or render the wrong UI) logged in, or vice versa."),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Why:")," ERB that conditionally wraps or alters a server-rendered component bakes the login state into the server HTML, but the client bundle renders from props alone:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="erb"><pre class="language-erb"><code class="language-erb"><span class="token erb language-erb"><span class="token delimiter punctuation"><%</span><span class="token ruby language-ruby"><span class="token comment"># BAD: the server HTML contains the admin toolbar, the client render doesn\'t know about it </span></span><span class="token delimiter punctuation">%></span></span>\n<span class="token erb language-erb"><span class="token delimiter punctuation"><%</span><span class="token ruby language-ruby"> <span class="token keyword">if</span> current_user<span class="token operator">&.</span>admin<span class="token operator">?</span> </span><span class="token delimiter punctuation">%></span></span>\n <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>admin-frame<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span><span class="token ruby language-ruby"> react_component<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Dashboard"</span></span><span class="token punctuation">,</span> <span class="token symbol">prerender</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span> </span><span class="token delimiter punctuation">%></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>\n<span class="token erb language-erb"><span class="token delimiter punctuation"><%</span><span class="token ruby language-ruby"> <span class="token keyword">else</span> </span><span class="token delimiter punctuation">%></span></span>\n <span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span><span class="token ruby language-ruby"> react_component<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Dashboard"</span></span><span class="token punctuation">,</span> <span class="token symbol">prerender</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span> </span><span class="token delimiter punctuation">%></span></span>\n<span class="token erb language-erb"><span class="token delimiter punctuation"><%</span><span class="token ruby language-ruby"> <span class="token keyword">end</span> </span><span class="token delimiter punctuation">%></span></span></code></pre></div>'}}),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Fix â props, not ERB:")," pass the user state as props and branch inside the component, so server and client render from the same inputs:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="erb"><pre class="language-erb"><code class="language-erb"><span class="token erb language-erb"><span class="token delimiter punctuation"><%=</span><span class="token ruby language-ruby"> react_component<span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Dashboard"</span></span><span class="token punctuation">,</span>\n <span class="token symbol">props</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">
1currentUser</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token symbol">admin</span><span class="token operator">:</span> current_user<span class="token operator">&.</span>admin<span class="token operator">?</span> <span class="token operator">||</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token symbol">name</span><span class="token operator">:</span> current_user<span class="token operator">&.</span>name <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>\n <span class="token symbol">prerender</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span> </span><span class="token delimiter punctuation">%></span></span></code></pre></div>'}}),"\n",s.createElement(n.h3,{id:"3-i18n-locale-drift",style:{position:"relative"}},s.createElement(n.a,{href:"#3-i18n-locale-drift","aria-label":"3 i18n locale drift permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"3. I18n locale drift"),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Symptom:")," translated strings mismatch â the server renders one language, the client another."),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Why:")," the server render uses ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">I18n.locale</code>'}})," from the Rails request, while the client may initialize its JS i18n library from ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">navigator.language</code>'}}),", a cookie, or a default that disagrees with Rails."),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Fix:")," drive the client locale from the same source as the server. React on Rails already passes ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">i18nLocale</code>'}})," and ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">i18nDefaultLocale</code>'}})," in the ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">railsContext</code>'}})," to every render function on both server and client:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="jsx"><pre class="language-jsx"><code class="language-jsx"><span class="token keyword">const</span> <span class="token function-variable function">MyApp</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">props<span class="token punctuation">,</span> railsContext</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>\n i18n<span class="token punctuation">.</span>locale <span class="token operator">=</span> railsContext<span class="token punctuation">.</span>i18nLocale<span class="token punctuation">;</span> <span class="token comment">// same value on server and client</span>\n <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">App</span></span> <span class="token spread"><span class="token punctuation">{</span><span class="token operator">...</span>props<span class="token punctuation">}</span></span> <span class="token punctuation">/></span></span><span class="token punctuation">;</span>\n<span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,"See the ",s.createElement(n.a,{href:"/linked-assets/3865ca1cad4761029d4420624fbce6cf/i18n.md"},"Internationalization guide")," for generating translation files."),"\n",s.createElement(n.h3,{id:"4-csrf-token-dependent-props-and-markup",style:{position:"relative"}}
1,s.createElement(n.a,{href:"#4-csrf-token-dependent-props-and-markup","aria-label":"4 csrf token dependent props and markup permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"4. CSRF-token-dependent props and markup"),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Symptom:")," forms or fetch wrappers that embed the CSRF token mismatch on every page load."),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Why:")," the token from ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">csrf_meta_tags</code>'}})," is per-session/request. If the server render embeds one token into component HTML (e.g., a hidden input rendered from a prop) and the client reads a fresh one from the meta tag, the values differ. With Rails action caching, the cached server HTML can even contain another user's masked token."),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Fix:")," never render the token during SSR. Read it client-side with React on Rails' helper when you actually submit:"),"\n",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token keyword">import</span> ReactOnRails <span class="token keyword">from</span> <span class="token string">\'react-on-rails/client\'</span><span class="token punctuation">;</span>\n\n<span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">\'/orders\'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>\n <span class="token literal-property property">method</span><span class="token operator">:</span> <span class="token string">\'POST\'</span><span class="token punctuation">,</span>\n <span class="token literal-property property">headers</span><span class="token operator">:</span> ReactOnRails<span class="token punctuation">.</span><span class="token function">authenticityHeaders</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token string-property property">\'Content-Type\'</span><span class="token operator">:</span> <span class="token string">\'application/json\'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>\n <span class="token literal-property property">body</span><span class="token operator">:</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>payload<span class="token punctuation">)</span><span class="token punctuation">,</span>\n<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div>'}}),"\n",s.createElement(n.p,null,"If a hidden field is unavoidable, populate it in ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useEffect</code>'}})," (client-only) instead of from a server-rendered prop."),"\n",s.createElement(n.h3,{id:"5-asset-hosts-and-url-helpers",style:{position:"relative"}},s.createElement(n.a,{href:"#5-asset-hosts-and-url-helpers","aria-label":"5 asset hosts and url helpers permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"5. Asset hosts and URL helpers"),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Symptom:")," ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text"><img></code>'}}),"/",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text"><link></code>'}})," attribute mismatches where the server URL has a CDN host (or different protocol) and the client-computed URL does not."),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Why:")," ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">config.asset_host</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">default_url_options</code>'}}),", and request-dependent helpers (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">request.base_url</code>'}}),") apply during the Rails-side render, while client code building URLs from relative paths or ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">window.location</code>'}})," produces different strings."),"\n",s.createElement(n.p,null,s.createElement(n.strong,null,"Fixes:")),"\n",s.createElement(n.ul,null,"\n",s.createElement(n.li,null,"Compute asset URLs once in Rails and pass them as props (",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">image_url</code>'}}),", not client-side string building). The ",s.createElement(n.a,{href:"/linked-assets/4d2a1aa88145b6d9ddf143fc8b2b7d37/images.md"},"images guide")," covers webpack-side asset imports that produce identical URLs in both bundles."),"\n",s.createElement(n.li,null,"Avoid ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">window.location</code>'}}),"-derived URLs during initial render; if needed, move them to ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useEffect</code>'}}),"."),"\n"),"\n",s.createElement(n.h3,{id:"6-anything-nondeterministic-in-render",style:{position:"relative"}}
1,s.createElement(n.a,{href:"#6-anything-nondeterministic-in-render","aria-label":"6 anything nondeterministic in render permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"6. Anything nondeterministic in render"),"\n",s.createElement(n.p,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Math.random()</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">Date.now()</code>'}}),", ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">crypto.randomUUID()</code>'}}),", or unstable iteration order used during render guarantees a mismatch. Generate the value once on the server, pass it as a prop, or compute it in ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useEffect</code>'}}),". For stable generated IDs across server and client, use React's ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useId()</code>'}})," â React on Rails Pro's streaming SSR already coordinates the required ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">identifierPrefix</code>'}})," between server and client for multi-root pages."),"\n",s.createElement(n.h2,{id:"general-fix-patterns",style:{position:"relative"}},s.createElement(n.a,{href:"#general-fix-patterns","aria-label":"general fix patterns permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"General fix patterns"),"\n",s.createElement(n.table,null,s.createElement(n.thead,null,s.createElement(n.tr,null,s.createElement(n.th,null,"Pattern"),s.createElement(n.th,null,"When to use"))),s.createElement(n.tbody,null,s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.strong,null,"Props, not ERB")),s.createElement(n.td,null,"Any request-dependent value (user, locale, URLs, feature flags): pass it into the component instead of branching in the view around server-rendered HTML.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.strong,null,"Render client-side only")),s.createElement(n.td,null,"Values that genuinely differ per client (clocks, viewport, ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">window</code>'}}),"-derived state): initialize state server-safe, fill in via ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">useEffect</code>'}}),".")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.strong,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">suppressHydrationWarning</code>'}}))),s.createElement(n.td,null,"A single text node/attribute that legitimately differs (e.g. a timestamp). Last resort, narrowest possible scope â it hides the warning, not the double render.")),s.createElement(n.tr,null,s.createElement(n.td,null,s.createElement(n.strong,null,s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">prerender: false</code>'}}))),s.createElement(n.td,null,"Components that are inherently client-only; skip SSR entirely rather than fighting mismatches.")))),"\n",s.createElement(n.h2,{id:"verifying-a-fi
1x",style:{position:"relative"}},s.createElement(n.a,{href:"#verifying-a-fix","aria-label":"verifying a fix permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Verifying a fix"),"\n",s.createElement(n.ol,null,"\n",s.createElement(n.li,null,"In development, load the page and confirm the ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">[ReactOnRails] Recoverable hydration error...</code>'}})," message no longer appears."),"\n",s.createElement(n.li,null,"Keep an ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">onRecoverableError</code>'}})," callback wired to your error reporter in production â hydration regressions are otherwise invisible (React recovers silently)."),"\n",s.createElement(n.li,null,"For ongoing protection, assert in an E2E test that no recoverable events fire on your critical pages (see ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">react_on_rails/spec/dummy/e2e/playwright/e2e/react_on_rails/root_error_callbacks.spec.js</code>'}})," in the React on Rails repo for a working example)."),"\n"),"\n",s.createElement(n.h2,{id:"related-documentation",style:{position:"relative"}},s.createElement(n.a,{href:"#related-documentation","aria-label":"related documentation permalink",className:"anchor before"},s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" height="20" version="1.1" viewBox="0 0 16 16" width="20"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}})),"Related documentation"),"\n",s.createElement(n.ul,null,"\n",s.createElement(n.li,null,s.createElement(n.a,{href:"/linked-assets/a246a38c35234402e2529f692e7b4644/client-vs-server-rendering.md"},"Client vs. Server Rendering")),"\n",s.createElement(n.li,null,s.createElement(n.a,{href:"/linked-assets/54e45a146503a9194b6540e0b1fa8747/react-server-rendering.md"},"React Server Rendering")),"\n",s.createElement(n.li,null,s.createElement(n.a,{href:"/linked-assets/a4b5616de140631526dc7a10050db702/debugging.md"},"Debugging React on Rails")),"\n",s.createElement(n.li,null,s.createElement(n.a,{href:"/linked-assets/3865ca1cad4761029d4420624fbce6cf/i18n.md"},"Internationalization")),"\n",s.createElement(n.li,null,s.createElement(n.a,{href:"https://react.dev/reference/react-dom/client/hydrateRoot"},"React ",s.createElement(n.span,{dangerouslySetInnerHTML:{__html:'<code class="language-text">hydrateRoot</code>'}})," error callback options")),"\n"))}var r=function(e){void 0===e&&(e={});const{wrapper:n}=Object.assign({},(0,t.R)(),e.components);return n?s.createElement(n,e,s.createElement(o,e)):o(e)},l=a(63679),c=a(95197),p=a(47388),i=a(23690),u=a(14487);const d=l.default.p.withConfig({displayName:"doc-article__RedirectFallback",componentId:"sc-tic2w5-0"})(["padding:2rem;"]),m=e=>{let{data:n,location:a,children:t}=e;const{headings:o}=n.article.childMdx,r=(0,c.X9)(a.pathname),[l,i]=s.useState(null),m=a.pathname.split("/"),g=m.indexOf("docs"),h=-1===g?a.pathname.length:m.slice(0,g+1).join("/").length,k=a.pathname.slice(0,h),y=a.pathname.slice(h+1).replace(/\/$/,"").split("/").filter((e=>""!==e)),E=y.length>1?y.slice(0,-1).join("/"):"";s.useEffect((()=>{if(!r)return;const{hash:e}=window.location;i(e);const n=""+r+e;window.location.href!==n&&window.location.replace(n)}),[r]);const f=null===r||null===l?null:""+r+l;return s.createElement(s.Fragment,null,null!==f?s.createElement(d,{role:"status","aria-live":"polite"},"Redirecting..."," ",s.createElement("a",{href:f,target:"_blank",rel:"noopener noreferrer"},"Click here if not redirected.")):s.createElement(p.A,{main:s.createElement(u.A,{body:t,headings:o,transformUrl:e=>{if(e.startsWith("http")||e.startsWith("#"))return e;const n=e.replace(/\.md$/,"/");
1if(n.startsWith("./"))return k+(""===E?"":"/"+E)+n.slice(1);if(n.startsWith("../")){if(""===E)return""+k+n.slice(2);const e=E.split("/").slice(0,-1).join("/");return k+(""===e?"":"/"+e)+n.slice(2)}return k+"/"+(""===E?"":E+"/")+n}})}))};function g(e){return s.createElement(m,e,s.createElement(r,e))}const h=e=>{let{data:n,location:a,pageContext:t}=e;const{headings:o}=n.article.childMdx,{siteUrl:r}=n.site.siteMetadata,{repoDisplayName:l}=t,p=o.find((e=>1===e.depth)),u=(0,c.X9)(a.pathname)||""+r.replace(/\/+$/,"")+a.pathname;return s.createElement(i.A,{title:p?p.value:l,description:p?p.value:l+" documentation",isArticle:!0,canonical:u,pathname:a.pathname})}}}]); 2//# sourceMappingURL=component---src-template-doc-article-tsx-content-file-path-cache-gatsby-source-git-react-on-rails-docs-oss-building-features-debugging-hydration-mismatches-md-6b8ba3d4d595eb86dc27.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.