Механизм SSR для получения данных и кэширования немного сложен в next js.
В этой статье мы узнаем, как улучшить время начальной загрузки с помощью SSR и получить высокоскоростную навигацию на стороне клиента с помощью CSR и React Query.
Мы создадим приложение для блога, используя JSON Placeholder API.
Здесь мы рассмотрим только важные разделы. Чтобы увидеть полный исходный код, посетите репозиторий github. Вы также можете посмотреть демонстрацию в реальном времени, чтобы получить более полное представление. React Query devtools доступен в этой демонстрации, так что вы можете проверить поток кэша.
Оглавление
- 1. Создайте новый проект
- 2. Настройте Hydration
- 3. Предварительная выборка и обезвоживание данных
- 4. Неглубокая маршрутизация
- 5. с-CSR HOC
- 6. обработка кода состояния 404
- 7. Заключение
- 8. Ссылки
1. Создание нового проекта
Сначала создайте проект nextjs:
yarn create next-app blog-app
or
npx create-next-app blog-app
Установим React Query и Axios:
yarn add @tanstack/react-query axios
or
npm install @tanstack/react-query axios
2. Настройка гидратации
Благодаря документам react query мы настроим гидратацию в _app.js :
//pages/_app.js
import { useState } from 'react';
import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { config } from 'lib/react-query-config';
function MyApp({ Component, pageProps }) {
// This ensures that data is not shared
// between different users and requests
const [queryClient] = useState(() => new QueryClient(config))
return (
<QueryClientProvider client={queryClient}>
// Hydrate query cache
<Hydrate state={pageProps.dehydratedState}>
<Component {...pageProps} />
</Hydrate>
</QueryClientProvider>
)
}
export default MyApp;
3. Предварительная выборка и обезвоживание данных
Прежде чем продолжить, отметим, что в версии 3 React Query по умолчанию кэшировал результаты запроса в течение 5 минут, а затем вручную собирал эти данные. Это значение по умолчанию применялось и к React Query на стороне сервера. Это приводило к большому потреблению памяти и зависанию процессов в ожидании завершения ручной сборки мусора. В версии 4 по умолчанию для серверного cacheTime теперь установлено значение Infinity, что фактически отключает ручную сборку мусора (процесс NodeJS очистит все после завершения запроса).
Теперь нам нужно выполнить предварительную выборку данных и обезвоживание queryClient в методе getServerSideProps
:
//pages/posts/[id].js
import { getPost } from 'api/posts';
import { dehydrate, QueryClient } from '@tanstack/react-query';
export const getServerSideProps = async (ctx) => {
const { id } = ctx.params;
const queryClient = new QueryClient()
// prefetch data on the server
await queryClient.fetchQuery(['post', id], () => getPost(id))
return {
props: {
// dehydrate query cache
dehydratedState: dehydrate(queryClient),
},
}
}
P.S : Мы использовали fetchQuery
вместо prefetchQuery
, потому что prefetchQuery
не выдает ошибку и не возвращает никаких данных. Подробнее об этом мы поговорим в разделе 6. Обработка кода состояния 404.
Теперь мы можем легко использовать эти предварительно полученные данные на нашей странице, не передавая никаких данных по реквизитам.
Для ясности давайте рассмотрим реализацию метода getPost
и хука usePost
:
//api/posts.js
import axios from 'lib/axios';
export const getPost = async id => {
const { data } = await axios.get('/posts/' + id);
return data;
}
//hooks/api/posts.js
import { useQuery } from '@tanstack/react-query';
import * as api from 'api/posts';
export const usePost = (id) => {
return useQuery(['post', id], () => api.getPost(id));
}
Теперь мы можем использовать этот хук usePost
для получения данных поста.
//pages/posts/[id].js
import { useRouter } from 'next/router';
import { usePost } from 'hooks/api/posts'
import Loader from 'components/Loader';
import Post from 'components/Post';
import Pagination from 'components/Pagination';
const PostPage = () => {
const { query: { id } } = useRouter();
const { data, isLoading } = usePost(id);
if (isLoading) return <Loader />
return (
<>
<Post id={data.id} title={data.title} body={data.body} />
<Pagination id={id} />
</>
)
}
// getServerSideProps implementation ...
// We talked about it in section 2
4. Неглубокая маршрутизация
Мы хотим управлять механизмом выборки и кэширования данных только на клиенте, поэтому нам нужно использовать shallow = true
prop в компоненте Link для навигации между страницами постов, чтобы не вызывать getServerSideProps
каждый раз. Это означает, что метод getServerSideProps
будет вызываться только тогда, когда пользователи непосредственно перейдут по URL поста, а не при навигации внутри приложения на стороне клиента.
У нас есть компонент Pagination для навигации между страницами, поэтому мы используем shallow = true
здесь:
//components/Pagination.jsx
import Link from 'next/link';
function PaginationItem({ index }) {
return (
<Link className={itemClassName} href={'/posts/' + index} shallow={true}>
{index}
</Link>
)
}
export default PaginationItem;
P.S : Мы использовали новый компонент link в nextjs v12.2, поэтому нам не нужно было использовать здесь тег <a>
.
5. with-CSR HOC
На данный момент неглубокая маршрутизация nextjs v12.2 работает только для изменений URL на текущей странице. предостережения по неглубокой маршрутизации nextjs
Это означает, что если вы перейдете от /posts/10
к /posts/15
с shallow = true
, то getServerSideProps
не будет вызван, но если вы перейдете от /home
к /posts/15
, то getServerSideProps
будет вызван, даже если вы используете неглубокую маршрутизацию, и это приведет к получению ненужных данных, даже если они доступны в кэше.
Я нашел решение, которое проверяет, является ли этот запрос к getServerSideProps
запросом навигации на стороне клиента или нет. Если это так, то возвращается пустой объект для props и предотвращается выборка данных на сервере.
Мы не можем предотвратить вызов getServerSideProps
при навигации между различными страницами, но мы можем предотвратить выборку ненужных данных в getServerSideProps
.
Вот реализация сCSR HOC :
//HOC/with-CSR.js
export const withCSR = (next) => async (ctx) => {
// check is it a client side navigation
const isCSR = ctx.req.url?.startsWith('/_next');
if (isCSR) {
return {
props: {},
};
}
return next?.(ctx)
}
Теперь мы должны обернуть наш getServerSideProps
этим HOC.
//pages/posts/[id].js
import { getPost } from 'api/posts';
import { dehydrate, QueryClient } from '@tanstack/react-query';
import { withCSR } from 'HOC/with-CSR'
export const getServerSideProps = withCSR(async (ctx) => {
const { id } = ctx.params;
const queryClient = new QueryClient()
await queryClient.fetchQuery(['post', id], () => getPost(id))
return {
props: {
dehydratedState: dehydrate(queryClient),
},
}
})
Если мы переходим с разных страниц на страницы постов, getServerSideProps
не будет получать никаких данных и просто вернет пустой объект для props.
6. Обработка кода состояния 404
Хотя Next.js отображает страницу ошибки, если пост недоступен, на самом деле он не отвечает кодом статуса ошибки.
Это означает, что, хотя вы видите ошибку 404, на самом деле страница отвечает кодом 200. Для поисковых систем это, по сути, означает: «Все прошло нормально, и мы нашли страницу». Вместо того чтобы ответить 404, что говорит поисковым системам о том, что страница не существует.
Чтобы решить эту проблему, давайте снова посмотрим на getServerSideProps
:
const Page = ({ isError }) => {
//show custom error component if there is an error
if (isError) return <Error />
return <PostPage />
}
export const getServerSideProps = withCSR(async (ctx) => {
const { id } = ctx.params;
const queryClient = new QueryClient();
let isError = false;
try {
await queryClient.fetchQuery(['post', id], () => getPost(id));
} catch (error) {
isError = true
ctx.res.statusCode = error.response.status;
}
return {
props: {
//also passing down isError state to show a custom error component.
isError,
dehydratedState: dehydrate(queryClient),
},
}
})
export default Page;
7. Заключение
Мы настроили механизм кэширования с возможностью предварительной выборки данных на сервере в контексте SSR. Мы также узнали, как использовать неглубокую маршрутизацию для более быстрой навигации на стороне клиента.
Вот живая демонстрация нашей реализации и репозиторий github для исходного кода.
Также я добавил React Query devtools в продакшн, чтобы вы могли понять, что происходит под капотом.
Я хотел бы выразить искреннюю благодарность @aly3n.
8. Ссылки
- JSON Placeholder API
- Настройка гидратации React Query
- React Query без ручной сборки мусора на стороне сервера
- nextjs неглубокая маршрутизация предостережения
- Предотвращение выборки данных в getServerSideProps при навигации на стороне клиента
- отвечать ошибкой 404 в Next.js
- Исходный код проекта
- Живая демонстрация