No articles found
Try different keywords or browse our categories
Error: Next.js & React.js Text content does not match server-rendered HTML
Learn how to resolve the 'Text content does not match server-rendered HTML' error in React. Complete guide with solutions for server-side rendering and hydration issues.
The ‘Text content does not match server-rendered HTML’ error is a common issue developers encounter when implementing server-side rendering (SSR) in React applications. This error occurs when there’s a mismatch between the HTML rendered on the server and what React expects during client-side hydration.
This comprehensive guide provides complete solutions to resolve the text content mismatch error with practical examples and SSR optimization techniques.
Understanding the Text Content Mismatch Error
React’s hydration process reconciles the server-rendered HTML with the client-side React component tree. When there’s a mismatch between server and client content, React warns about the inconsistency to maintain proper functionality.
Common Error Messages:
Text content does not match server-rendered HTMLWarning: Text content did not matchServer: "Loading..." Client: "Hello World"Hydration failed because the initial UI does not match what was rendered on the server
Common Causes and Solutions
1. Conditional Rendering Based on Environment
The most common cause is rendering different content on server vs client based on environment detection.
❌ Problem Scenario:
// This will cause a mismatch
function BadComponent() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
// This only runs on client
setIsClient(true);
}, []);
return (
<div>
{/* ❌ Server renders "Loading..." but client renders "Client Side" */}
{isClient ? 'Client Side Content' : 'Loading...'}
</div>
);
}
✅ Solution: Use useEffect for Client-Side Updates
// Correct approach - render same content initially
function GoodComponent() {
const [content, setContent] = useState('Loading...');
useEffect(() => {
// Update content after hydration
setContent('Client Side Content');
}, []);
return <div>{content}</div>;
}
// Alternative: Use a client-side only component
function ClientOnly({ children }) {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
return isMounted ? children : null;
}
function BetterComponent() {
return (
<div>
<ClientOnly>
<p>Client Side Content</p>
</ClientOnly>
</div>
);
}
2. Time-Based Content Differences
Rendering different content based on time can cause mismatches.
❌ Problem Scenario:
// This will cause a mismatch if server and client times differ
function BadTimeComponent() {
const [currentTime, setCurrentTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>Current Time: {currentTime}</div>;
}
✅ Solution: Initialize with Server-Safe Values
// Correct approach - use server-safe initial value
function GoodTimeComponent() {
const [currentTime, setCurrentTime] = useState(() => {
// Use a placeholder that works on both server and client
if (typeof window !== 'undefined') {
return new Date().toLocaleTimeString();
}
return 'Loading...'; // Server-safe placeholder
});
useEffect(() => {
const updateTimer = () => {
setCurrentTime(new Date().toLocaleTimeString());
};
updateTimer(); // Update immediately on client
const timer = setInterval(updateTimer, 1000);
return () => clearInterval(timer);
}, []);
return <div>Current Time: {currentTime}</div>;
}
Solution 1: Proper SSR-Safe Conditional Rendering
Implement conditional rendering that works consistently on both server and client.
// SSR-safe conditional rendering
function SSRSafeComponent() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return (
<div>
{/* ✅ Render the same initial content */}
<p>{isClient ? 'Client Environment' : 'Server Environment'}</p>
{/* ✅ Use conditional rendering that doesn't cause mismatches */}
{isClient && <div>This only renders on client</div>}
{/* ✅ Or use a more sophisticated approach */}
<SSRConditional>
<ClientContent />
</SSRConditional>
</div>
);
}
// Custom component for SSR-safe conditional rendering
function SSRConditional({ children }) {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return isClient ? children : <div>Loading...</div>;
}
Solution 2: Using Next.js Built-in Solutions
Leverage Next.js features to handle SSR mismatches.
// Next.js dynamic imports with SSR disabled
import dynamic from 'next/dynamic';
// ✅ Dynamic import with SSR disabled
const ClientOnlyComponent = dynamic(
() => import('../components/ClientOnlyComponent'),
{ ssr: false }
);
// ✅ Dynamic import with loading component
const LazyComponent = dynamic(
() => import('../components/LazyComponent'),
{
loading: () => <p>Loading...</p>,
ssr: false
}
);
// Next.js page component
export default function MyPage() {
return (
<div>
<h1>Server-rendered content</h1>
<ClientOnlyComponent />
<LazyComponent />
</div>
);
}
// Using Next.js router for client-side detection
import { useRouter } from 'next/router';
function RouterBasedComponent() {
const router = useRouter();
const [isReady, setIsReady] = useState(false);
useEffect(() => {
if (router.isReady) {
setIsReady(true);
}
}, [router.isReady]);
return (
<div>
{isReady ? (
<p>Client-side content</p>
) : (
<p>Loading...</p>
)}
</div>
);
}
Solution 3: Custom Hooks for SSR Detection
Create custom hooks to handle server/client detection safely.
// Custom hook for SSR-safe environment detection
function useIsomorphicLayoutEffect() {
return typeof window !== 'undefined'
? useEffect
: (callback) => callback();
}
// Hook to detect client-side environment
function useIsClient() {
const [isClient, setIsClient] = useState(false);
useIsomorphicLayoutEffect(() => {
setIsClient(true);
}, []);
return isClient;
}
// Component using the custom hook
function IsomorphicComponent() {
const isClient = useIsClient();
return (
<div>
<p>Environment: {isClient ? 'Client' : 'Server'}</p>
{isClient && <div>Client-only content</div>}
</div>
);
}
// Hook for SSR-safe data fetching
function useSSRData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const isClient = useIsClient();
useEffect(() => {
if (isClient) {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
};
fetchData();
}
}, [isClient, url]);
return { data, loading };
}
// Component using SSR-safe data fetching
function SSRDataComponent({ url }) {
const { data, loading } = useSSRData(url);
if (loading) {
return <div>Loading...</div>;
}
return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
}
Solution 4: Proper State Initialization
Initialize state with values that work on both server and client.
// Proper state initialization for SSR
function ProperStateInit() {
// ✅ Initialize with server-safe values
const [userAgent, setUserAgent] = useState(() => {
if (typeof window !== 'undefined') {
return window.navigator.userAgent;
}
return 'Server'; // Server-safe default
});
const [windowSize, setWindowSize] = useState(() => {
if (typeof window !== 'undefined') {
return {
width: window.innerWidth,
height: window.innerHeight
};
}
return { width: 0, height: 0 }; // Server-safe default
});
useEffect(() => {
// Update values after hydration
setUserAgent(window.navigator.userAgent);
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
handleResize(); // Set initial values
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return (
<div>
<p>User Agent: {userAgent}</p>
<p>Window Size: {windowSize.width} x {windowSize.height}</p>
</div>
);
}
// Component with proper date initialization
function ProperDateComponent() {
const [currentDate, setCurrentDate] = useState(() => {
if (typeof window !== 'undefined') {
return new Date().toISOString();
}
return new Date().toISOString(); // Same value for both environments
});
useEffect(() => {
const timer = setInterval(() => {
setCurrentDate(new Date().toISOString());
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>Current Date: {new Date(currentDate).toLocaleString()}</div>;
}
Solution 5: Error Boundary for Hydration Issues
Use error boundaries to handle hydration errors gracefully.
// Error boundary for hydration issues
import React from 'react';
class HydrationErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
// Check if this is a hydration error
if (error.message && error.message.includes('hydration')) {
return { hasError: true, error };
}
return { hasError: false };
}
componentDidCatch(error, errorInfo) {
console.error('Hydration error caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="hydration-error-boundary">
<h2>Hydration Error Detected</h2>
<p>There was an issue with server-client content matching.</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Retry
</button>
</div>
);
}
return this.props.children;
}
}
// Component that might cause hydration issues
function PotentiallyProblematicComponent() {
const [content, setContent] = useState('Server Content');
useEffect(() => {
// This might cause hydration mismatch
setContent('Client Content');
}, []);
return <div>{content}</div>;
}
// Safe wrapper with error boundary
function SafeHydrationWrapper() {
return (
<HydrationErrorBoundary>
<PotentiallyProblematicComponent />
</HydrationErrorBoundary>
);
}
Solution 6: Next.js getServerSideProps and getStaticProps
Use Next.js data fetching methods properly to avoid mismatches.
// pages/ssr-example.js
export async function getServerSideProps() {
// Server-side data fetching
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return {
props: {
serverData: data,
timestamp: new Date().toISOString() // Server timestamp
}
};
}
export default function SSRPage({ serverData, timestamp }) {
const [clientData, setClientData] = useState(null);
const [clientTimestamp, setClientTimestamp] = useState(timestamp);
useEffect(() => {
// Client-side updates
setClientTimestamp(new Date().toISOString());
// Fetch client-specific data if needed
fetch('/api/client-data')
.then(res => res.json())
.then(data => setClientData(data));
}, []);
return (
<div>
<h1>SSR Example</h1>
<p>Server timestamp: {new Date(timestamp).toLocaleString()}</p>
<p>Client timestamp: {new Date(clientTimestamp).toLocaleString()}</p>
<div>
<h3>Server Data:</h3>
<pre>{JSON.stringify(serverData, null, 2)}</pre>
</div>
{clientData && (
<div>
<h3>Client Data:</h3>
<pre>{JSON.stringify(clientData, null, 2)}</pre>
</div>
)}
</div>
);
}
// Using getStaticProps with revalidation
export async function getStaticProps() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return {
props: {
data,
timestamp: new Date().toISOString()
},
revalidate: 60 // Revalidate every 60 seconds
};
}
export default function StaticPage({ data, timestamp }) {
const [localData, setLocalData] = useState(data);
useEffect(() => {
// Update with client-specific data if needed
setLocalData(prev => ({
...prev,
clientSpecific: true
}));
}, []);
return (
<div>
<h1>Static Generation Example</h1>
<p>Generated at: {new Date(timestamp).toLocaleString()}</p>
<pre>{JSON.stringify(localData, null, 2)}</pre>
</div>
);
}
Solution 7: React 18 Concurrent Features
Use React 18 features to handle SSR mismatches better.
// Using React 18 Suspense for SSR
import { Suspense } from 'react';
// Component that suspends during data fetching
function AsyncComponent() {
const data = useAsyncData(); // Custom hook that throws a promise
return <div>{JSON.stringify(data)}</div>;
}
// Wrapper with Suspense
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
);
}
// Custom hook for async data with Suspense
function useAsyncData() {
const [resource, setResource] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/data');
const data = await response.json();
setResource(data);
};
fetchData();
}, []);
if (resource === null) {
throw new Promise(resolve => setTimeout(resolve, 1000)); // Simulate loading
}
return resource;
}
// Using React 18's useId for stable IDs
import { useId } from 'react';
function FormWithId() {
const id = useId();
return (
<div>
<label htmlFor={id}>Input Label</label>
<input id={id} type="text" />
</div>
);
}
Solution 8: Testing SSR Components
Create tests to verify SSR compatibility.
// Testing SSR components
import { renderToString } from 'react-dom/server';
import { render, screen, waitFor } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
// Component to test
function SSRTestComponent() {
const [content, setContent] = useState('Server Content');
useEffect(() => {
setContent('Client Content');
}, []);
return <div>{content}</div>;
}
// Test SSR rendering
describe('SSR Component', () => {
test('should render without hydration errors', () => {
// Test server-side rendering
const serverRendered = renderToString(<SSRTestComponent />);
expect(serverRendered).toContain('Server Content');
// Test client-side hydration
render(<SSRTestComponent />);
// Initially should show server content
expect(screen.getByText('Server Content')).toBeInTheDocument();
// After hydration, should update to client content
waitFor(() => {
expect(screen.getByText('Client Content')).toBeInTheDocument();
});
});
});
// Mock window object for testing
beforeEach(() => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
});
Solution 9: Performance Optimization for SSR
Optimize SSR performance while maintaining content consistency.
// Performance-optimized SSR component
import { useMemo, useCallback } from 'react';
function OptimizedSSRComponent({ data }) {
// ✅ Memoize expensive calculations
const processedData = useMemo(() => {
if (!data) return null;
// Expensive processing that should be memoized
return data.map(item => ({
...item,
processed: true,
id: item.id
}));
}, [data]);
// ✅ Memoize functions to prevent unnecessary re-renders
const handleClick = useCallback((id) => {
console.log('Item clicked:', id);
}, []);
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return (
<div>
<h2>Optimized SSR Component</h2>
{isClient ? (
<p>Client Environment</p>
) : (
<p>Server Environment</p>
)}
{processedData && (
<ul>
{processedData.map(item => (
<li key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</li>
))}
</ul>
)}
</div>
);
}
// Component with proper loading states
function LoadingOptimizedComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
// Only fetch on client
if (typeof window !== 'undefined') {
fetchData();
}
}, []);
if (loading) {
return <div className="skeleton-loader">Loading...</div>;
}
return <div>{data && <pre>{JSON.stringify(data, null, 2)}</pre>}</div>;
}
Performance Considerations
Efficient SSR Rendering:
// Optimized SSR patterns
function EfficientSSR() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
// ✅ Use stable initial values
const [count, setCount] = useState(0);
// ✅ Memoize expensive operations
const expensiveValue = useMemo(() => {
// Only calculate on client
return isClient ? performExpensiveOperation() : 0;
}, [isClient]);
return (
<div>
<p>Count: {count}</p>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
{/* ✅ Conditional rendering that doesn't cause mismatches */}
{isClient && <div>Client-specific content</div>}
</div>
);
}
function performExpensiveOperation() {
// Simulate expensive operation
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
return result;
}
Security Considerations
Safe SSR Content:
// Secure SSR content handling
function SecureSSRComponent({ userContent }) {
const [sanitizedContent, setSanitizedContent] = useState('');
useEffect(() => {
// Sanitize content on client if needed
if (userContent) {
const sanitized = sanitizeContent(userContent);
setSanitizedContent(sanitized);
}
}, [userContent]);
return (
<div>
{/* ✅ Always sanitize user-generated content */}
<div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
</div>
);
}
function sanitizeContent(content) {
// Basic sanitization - in production, use a proper library like DOMPurify
return content
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Common Mistakes to Avoid
1. Environment-Specific Rendering:
// ❌ Don't do this
function BadEnvironmentComponent() {
const [content, setContent] = useState(
typeof window !== 'undefined' ? 'Client' : 'Server'
);
return <div>{content}</div>; // This will cause mismatches
}
2. Time-Based Content:
// ❌ Don't do this
function BadTimeComponent() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>{time}</div>; // Server and client will have different times
}
3. Window/Document Access During Render:
// ❌ Don't do this
function BadWindowAccess() {
const [dimensions, setDimensions] = useState({
width: window.innerWidth, // ❌ Accessing window during render
height: window.innerHeight
});
return <div>{dimensions.width} x {dimensions.height}</div>;
}
Alternative Solutions
Using React DevTools:
// Component optimized for React DevTools
function DevToolsOptimizedComponent() {
const [data, setData] = useState(null);
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
// Fetch data after hydration
fetch('/api/data').then(res => res.json()).then(setData);
}, []);
return (
<div data-testid="ssr-optimized">
{isClient ? (
<pre>{JSON.stringify(data, null, 2)}</pre>
) : (
<div>Loading...</div>
)}
</div>
);
}
Feature Detection:
// Check for SSR compatibility
function SSRCompatibleComponent() {
const [isSSR, setIsSSR] = useState(true);
useEffect(() => {
setIsSSR(false); // We're now on the client
}, []);
return (
<div>
{isSSR ? 'Server' : 'Client'}
</div>
);
}
Troubleshooting Checklist
When encountering the text content mismatch error:
- Check Conditional Rendering: Ensure server and client render the same initial content
- Verify Data Fetching: Confirm data is available during SSR or use loading states
- Review Environment Detection: Avoid environment-specific rendering during initial render
- Test Hydration: Verify client-side updates don’t conflict with server content
- Use React DevTools: Inspect the component tree for mismatch issues
- Check Timing: Ensure time-based content is handled properly
- Validate Dynamic Imports: Confirm SSR settings for dynamic components
Conclusion
The ‘Text content does not match server-rendered HTML’ error occurs when there’s a mismatch between server and client content during React’s hydration process. By implementing proper SSR patterns, using environment detection safely, and maintaining consistent initial rendering, you can resolve these mismatches and ensure smooth server-side rendering in your React applications.
The key to resolving this error is ensuring that server and client initially render the same content, then allowing client-side updates after hydration. Whether you’re working with Next.js, React Router, or custom SSR implementations, the solutions provided in this guide will help you handle SSR content matching appropriately in your React applications.
Remember to always test your SSR implementations, use proper loading states, and implement safe environment detection to ensure consistent rendering across server and client environments.
Related Articles
How to Fix & Solve React.js and Next.js Hydration Error: Complete Guide 2026
Learn how to fix React.js and Next.js hydration errors with step-by-step solutions. This guide covers client-server mismatch issues, dynamic content rendering, and best practices for seamless SSR.
How to Fix React app works locally but not after build Error
Learn how to fix React apps that work locally but fail after build. Complete guide with solutions for production deployment and build optimization.
How to Solve React Blank Page After Deploy & Build Error Tutorial
Learn how to fix React blank page errors after deployment. Complete guide with solutions for production builds and deployment optimization.