Search code examples
javascriptreactjsnext.jsnext.js14

Next 14 Hydration failed error on my custom stroke text component


I recentlly started to build an app with Next 14 and in my app, I need some text with a stroke. Unfortunately, the CSS stroke doesn't work as I would like because it acts like an inner stroke so I created a custom component that overlays a text over the same text with a stroke, giving the appearance that the stroke is an outer stroke rather than an inner stroke.

The issue that I encounter is that I get this hydration error:

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.
See more info here: https://nextjs.org/docs/messages/react-hydration-error

In HTML, <p> cannot be a descendant of <p>.
This will cause a hydration error.


...
  <StrokeText>
    <p>
    ^^^
      <p>

and I don't know how to fix it. I am not even sure where to begin.

This is the component:

import React from 'react';

import styles from './styles.module.css';

type StrokeTextType = {
  fontSize: string;
  color: string;
  strokeSize: string;
  strokeColor: string;
  text: string;
  align?: 'left' | 'center' | 'right';
  fontWeight?: number | string;
  margin?: string;
  fontFamily?: string;
  children?: React.ReactElement;
};

const StrokeText = ({
  fontSize,
  color,
  strokeSize,
  strokeColor,
  text,
  align,
  fontWeight,
  margin,
  fontFamily,
  children,
}: StrokeTextType) => {
  return (
    <p
      className={styles.text}
      title={text}
      style={
        {
          color,
          fontSize,
          fontWeight,
          fontFamily,
          margin,
          textAlign: align,
          '--stroke-size': strokeSize,
          '--stroke-color': strokeColor,
        } as React.CSSProperties
      }
    >
      {children ? children : text}
    </p>
  );
};

export default StrokeText;

And this is how I used it:

            <StrokeText
              text='Ok, you win, but it was close'
              color='white'
              fontSize={isMobile ? '15px' : '20px'}
              strokeSize='5px'
              strokeColor='black'
              fontWeight={400}
              align='center'
              margin={isMobile ? '0 0 10px 0' : '0'}
            >
              <p>
                Ok, you win,{' '}
                <span style={{ color: '#FFCC00' }}>but</span> it was close.
              </p>
            </StrokeText>

Solution

  • Its because your wrapper component StrokeText is wrapping its children inside an p tag which is an block element and it can contain inline elements not another block element

    To Fix the Hydration error you can use div instead of p in the StrokeText component

    like this

    import React from 'react';
    
    import styles from './styles.module.css';
    
    type StrokeTextType = {
      fontSize: string;
      color: string;
      strokeSize: string;
      strokeColor: string;
      text: string;
      align?: 'left' | 'center' | 'right';
      fontWeight?: number | string;
      margin?: string;
      fontFamily?: string;
      children?: React.ReactElement;
    };
    
    const StrokeText = ({
      fontSize,
      color,
      strokeSize,
      strokeColor,
      text,
      align,
      fontWeight,
      margin,
      fontFamily,
      children,
    }: StrokeTextType) => {
      return (
        <div
          className={styles.text}
          title={text}
          style={
            {
              color,
              fontSize,
              fontWeight,
              fontFamily,
              margin,
              textAlign: align,
              '--stroke-size': strokeSize,
              '--stroke-color': strokeColor,
            } as React.CSSProperties
          }
        >
          {children ? children : text}
        </div>
      );
    };
    
    export default StrokeText;