RUDE

Ошибки гидратации NextJS (текстовое содержимое не соответствует HTML-коду, отображаемому сервером).

У меня есть две ошибки только тогда, когда приложение развернуто, а не в режиме разработки и не в локальной сборке, что является совсем другой проблемой.

1-я ошибка: Текстовое содержимое не соответствует HTML, отображаемому сервером.

2-я ошибка: Произошла ошибка при гидратации. Поскольку ошибка произошла за пределами границы приостановки, весь корень переключится на клиентский рендеринг.

Я обнаружил, что ошибки вызваны форматированием времени, которое я получаю от API в формате метки времени Unix (current.dt), но я не знаю, как еще с этим справиться. Должен ли я форматировать дату на сервере, то есть в getServerSideProps? Или эти ошибки вызваны чем-то другим?

Редактировать: работающее приложение с ошибками: https://weather-test-mu.vercel.app/

Погода Дисплей:

import type { Current, Location } from '../../types/typeWeatherApi';

const WeatherDisplay = ({
    location,
    current,
}: {
    location: Location;
    current: Current;
}) => {
    // This causes the errors:
    const hour = ('0' + new Date(current.dt * 1000).getHours()).slice(-2);
    const minutes = ('0' + new Date(current.dt * 1000).getMinutes()).slice(-2);
    const currentTime = `${hour}:${minutes}`;

    return (
        <article>
            <h1>{location.name}</h1>
            <p>{location.country}</p>
            <p>{current.feels_like}</p>
            {/* This causes the error: */}
            <p>{currentTime}</p>
        </article>
    );
};

export default WeatherDisplay;

Индексная страница с getServerSideProps:

import axios from 'axios';
import { useState } from 'react';

import type { NextPage, GetServerSideProps } from 'next';
import type { Data, Location } from '../types/typeWeatherApi';

import { getCurrentWeather } from './api/currentWeather';
import { getCurrentLocation } from './api/currentLocation';

import WeatherDisplay from '../components/weather-display/weather-display';

const Home: NextPage = ({ initialWeather, initialLocation }: any) => {
    const [location, setLocation] = useState<Location>(initialLocation);
    const [weatherData, setWeatherData] = useState<Data>(initialWeather);
    const [units, setUnits] = useState('metric');
    const [lang, setLang] = useState('en');

    const getGeolocationData = () => {
        navigator.geolocation.getCurrentPosition(
            (position) => {
                axios
                    .get(
                        `/api/currentLocation?lon=${position.coords.longitude}&lat=${position.coords.latitude}`
                    )
                    .then((response) => setLocation(response.data[0]));
            },
            (error) => {
                console.warn(`ERROR(${error.code}): ${error.message}`);
            },
            {
                timeout: 10000,
                maximumAge: 0,
            }
        );
    };

    const getCurrentWeather = async () => {
        await axios
            .get(
                `/api/currentWeather?lon=${location.lon}&lat=${location.lat}&units=${units}&lang=${lang}`
            )
            .then((response) => setWeatherData(response.data))
            .catch((error) => console.error(error));
    };

    return (
        <>
            <section>
                <div>
                    <p>Latitude: {location.lat}</p>
                    <p>Longitude: {location.lon}</p>
                </div>
                <button onClick = {getGeolocationData}>Get Current Location</button>

                <button onClick = {getCurrentWeather}>Get Current Weather</button>
            </section>
            <section className = "current-weather">
                <WeatherDisplay location = {location} current = {weatherData.current} />
            </section>
        </>
    );
};

export const getServerSideProps: GetServerSideProps = async () => {
    const defaultWeatherQuery = {
        lat: '51.5072',
        lon: '0.1276',
        exclude: '',
        units: 'metric',
        lang: 'en',
    };

    const defaultLocationQuery = {
        lat: defaultWeatherQuery.lat,
        lon: defaultWeatherQuery.lon,
    };

    const defaultWeather = await getCurrentWeather(defaultWeatherQuery);
    const defaultLocation = await getCurrentLocation(defaultLocationQuery);

    return {
        props: {
            initialWeather: defaultWeather,
            initialLocation: defaultLocation[0],
        },
    };
};

export default Home;


4
676
1

Ответ:

Решено

Я получил ответ от Обсуждения на GitHub на next.js, я вставлю его сюда для тех, кто столкнулся с той же проблемой, что и я:

Цитата ледянойДжозеф:

Hi, Yeah so this two errors are combined. Because the client, on the first frame, needs to see the same HTML as the server sent over, in order to place event listeners and place siblings and children correctly, if there's an error while this is being done, React logs an error. This has happened all the way back to React 17 AFAIK. Problem number 2 kicks in with the new rendering root, which sees this as a rendering error, which is unfriendly to concurrent features, so hydration fails and it throws the entire thing out the window. At least that's how I interpret the second error. Lots of people just ignored error number 1, during the entire 2 years React 17 was out, not saying you did, but many use libraries that did, and others just ignored them. Possible work around Time and randomness are two of the things that most commonly produce this. I would attack this problem like this: Let the server render the time, but in UTC format Optionally, put CSS that makes this hidden, but still present on the layout (not display none) From the client, update to the correct time zone, also make the time visible import { CSSProperties, useEffect, useState } from "react"; import { Example } from "../components/Example"; const TimeDisplay = ({ time }: { time: number }) => { const [currentTime, setCurrentTime] = useState(() => { const hour = ("0" + new Date(time * 1000).getUTCHours()).slice(-2); const minutes = ("0" + new Date(time * 1000).getUTCMinutes()).slice(-2); return `${hour}:${minutes} UTC`; }); useEffect(() => { setCurrentTime(() => { const hour = ("0" + new Date(time * 1000).getHours()).slice(-2); const minutes = ("0" + new Date(time * 1000).getMinutes()).slice(-2); return `${hour}:${minutes}`; }); }, [time]); // optionally make this content take space, but remain invisible, to avoid layout shifts // it's better to use a CSS class instead const style: CSSProperties = { visibility: currentTime.includes("UTC") ? "hidden" : "visible", }; return ( <article> <p style = {style}>{currentTime}</p> </article> ); }; I'd recommend also mixing in the DateTimeFormat API, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat, which can take care of showing the time zone for you: const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0, 200)); options = { hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' }; console.info(new Intl.DateTimeFormat('sv-SE', options).format(date)); If I run the above on a repl, I get 3:00:00 AM UTC (this would be what your server sends), but if I run it on the browsers I get, 4:00:00 CET. The source code of you page contains the time seen by the server. Since users arrive to you from different time zones, you have to account for a frame where you show UTC, and then you show the time for the users time zone. Combining this with CSS visibility is good, because you can avoid layout shifts and such, the time will take its place, but it won't be visible. Robots, crawlers still see the time, and for users with JS disabled, you could add a tag that makes the date visible again.