Поднимите настройку Next js + GraphQL + TypeScript на новый уровень
В этом посте мы покажем вам, как воспользоваться Next.js возможности в сочетании с Apollo и GraphQL. Наше внимание будет сосредоточено на интеграции Apollo Client с некоторыми Next.js наиболее важные функции, включая рендеринг на стороне сервера (SSR), статическую генерацию сайта (SSG) и инкрементную статическую регенерацию (ISR). Однако, прежде чем мы углубимся в специфику, давайте кратко рассмотрим, что включает в себя каждая из этих функций.
Метод SSR
Рендеринг на стороне сервера (SSR) - это метод, который позволяет вам предварительно отрисовывать страницы вашего веб-сайта на сервере и предоставлять их клиенту в виде HTML-файлов. С SSR
начальная загрузка страницы происходит быстрее, потому что сервер отправляет HTML-файл с отображаемым содержимым, а не отправляет пустую страницу, которую необходимо заполнить содержимым после загрузки JavaScript.
В дополнение к увеличению времени начальной загрузки страницы SSR также может улучшить SEO, поскольку поисковые системы могут легко сканировать и индексировать ваши предварительно отрисованные страницы. SSR также может помочь оптимизировать производительность для пользователей с медленным подключением к Интернету или менее мощными устройствами.
SSR может быть реализован многими способами, но Next.js предоставляет встроенный API, который упрощает процесс. С помощью Next.js , вы можете легко включить SSR для ваших компонентов React, что позволит вам воспользоваться преимуществами SSR без необходимости писать много серверного кода.
Метод SSG и функция ISR
Статическая генерация сайтов (SSG) - это метод создания веб-сайтов, который предварительно отображает все страницы веб-сайта во время сборки и предоставляет эти предварительно отрисованные страницы пользователям, в отличие от генерации страниц по запросу. Хотя SSG - это быстрый и масштабируемый подход к созданию веб-сайтов, у него есть ограничение, заключающееся в том, что каждый раз, когда вы хотите обновить какой-либо контент, вам необходимо перестраивать весь сайт. Инкрементная статическая регенерация (ISR) является особенностью Next.js это устраняет это ограничение, позволяя вам обновлять определенные страницы вашего веб-сайта без перестройки всего сайта.
Важно отметить, что SSG и ISR предлагают преимущества SSR, а также являются более производительными.
Кэш Apollo и SSR/SSG/ISR
Apollo Client предоставляет мощную систему кэширования, которая позволяет вам управлять состоянием вашего приложения и быстро получать доступ к данным без необходимости делать сетевой запрос. Когда вы используете Apollo Client для выполнения запроса GraphQL, результаты автоматически сохраняются в кэше, что значительно ускоряет последующие запросы к тем же данным.
Для достижения SSR/SSG/IS в Next.js приложение, вы можете сделать сетевой запрос на сервере с помощью Apollo Client и предварительно заполнить кэш данными. Это позволяет данным быть немедленно доступными на стороне клиента, без необходимости дополнительных сетевых запросов.
Используя кэш в сочетании с SSR/SSG/ISR, вы можете значительно повысить производительность и удобство использования вашего приложения, гарантируя, что данные всегда доступны быстро и надежно.
Вот небольшая диаграмма.
Настройка приложения
Для начала мы собираемся использовать этот шаблон, который уже имеет настройки TypeScript и Apollo в Next js.
Давай приступим к работе.
1. Установите следующие зависимости.
npm install lodash-es deepmerge
Позже эти библиотеки понадобятся нам для изменения кэша Apollo.
2. Установите типы для lodash-es
npm install --save-dev @types/lodash-es
3. Создайте папку под названием apollo
с файлом index.ts
внутри.
4. Внутри этого файла создайте функцию, которая инициализирует клиент apollo.
import { ApolloClient, InMemoryCache } from '@apollo/client';
const COUNTRIES_API = 'https://countries.trevorblades.com';
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
uri: COUNTRIES_API,
cache: new InMemoryCache(),
});
}
Это должно выглядеть знакомо, теперь это просто абстрагировано в функцию. Здесь мы создаем экземпляр клиента Apollo, свойство ssr
здесь очень важно, оно определяет, используется ли клиент в режиме рендеринга на стороне сервера (true
) или в режиме рендеринга на стороне клиента (false
). Все это основано на том, определено window
или нет.
5. Внутри того же файла создайте функцию с именем initializeApollo со следующим содержимым.
import merge from 'deepmerge';
import isEqual from 'lodash-es/isEqual';
import { ApolloClient, InMemoryCache } from '@apollo/client';
let apolloClient: ApolloClient<NormalizedCacheObject> | null;
function createApolloClient() {
//...
}
export function initializeApollo(initialState?: any) {
const _apolloClient = apolloClient ?? createApolloClient();
if (initialState) {
const existingCache = _apolloClient.cache.extract();
const data = merge(initialState, existingCache, {
arrayMerge: (destinationArray, sourceArray) => [
...sourceArray,
...destinationArray.filter((d) =>
sourceArray.every((s) => !isEqual(d, s))
),
],
});
_apolloClient.cache.restore(data);
}
if (typeof window === 'undefined') {
return _apolloClient;
}
if (!apolloClient) {
apolloClient = _apolloClient;
}
return _apolloClient;
}
Эта функция инициализирует клиент Apollo. Он создает новую переменную с именем _apolloClient
с существующим apolloClient
, если он существует, или с новым экземпляром createApolloClient()
, если он не существует. Это гарантирует наличие только одного экземпляра клиента во всем приложении. Если есть начальное состояние, он извлекает существующий кеш из _apolloClient
, объединяет его с initialState
с помощью функции merge()
, а затем восстанавливает объединенные данные в кеш. Это позволяет использовать начальное состояние в любых последующих запросах.
6. Последней функцией в этом файле будет addApolloState
.
import merge from 'deepmerge';
import isEqual from 'lodash/isEqual';
import { ApolloClient, InMemoryCache } from '@apollo/client';
let apolloClient: ApolloClient<NormalizedCacheObject> | null;
export const APOLLO_STATE_PROP_NAME = '__APOLLO_STATE__';
function createApolloClient() {
// ...
}
export function initializeApollo(initialState?: any) {
// ...
}
export function addApolloState(
client: ApolloClient<NormalizedCacheObject>,
pageProps: any
) {
if (pageProps?.props) {
pageProps.props[APOLLO_STATE_PROP_NAME] = client.cache.extract();
}
return pageProps;
}
Эта служебная функция, называемая addApolloState
, принимает два параметра: экземпляр клиента Apollo и объект, содержащий реквизиты, передаваемые на страницу Next.js с именем pageProps
.
Затем функция проверяет, есть ли у объекта pageProps
свойство, называемое props
. Если это так, он добавляет состояние кэша клиента Apollo в объект pageProps.props
, используя идентификатор APOLLO_STATE_PROP_NAME
.
Наконец, функция возвращает объект pageProps
с добавленным к нему состоянием кэша клиента Apollo. Эта функция будет использоваться в функции getStaticProps/getServerSideProps
страницы Next.js для внедрения состояния кэша клиента Apollo.
7. Теперь давайте создадим хук, который поможет нам получить клиент Apollo и передать его нашему приложению. Создайте папку с именем hooks
с файлом с именем useApollo.ts
.
import { useMemo } from 'react';
import { APOLLO_STATE_PROP_NAME, initializeApollo } from '../apollo';
function useApollo(pageProps: any) {
const state = pageProps[APOLLO_STATE_PROP_NAME];
const client = useMemo(() => initializeApollo(state), [state]);
return client;
}
export default useApollo;
Хук сначала извлекает состояние кэша клиента Apollo из pageProps
, используя идентификатор APOLLO_STATE_PROP_NAME
. Затем он вызывает функцию initializeApollo
, которую мы создали ранее, чтобы получить либо существующий, либо новый экземпляр клиента Apollo.
8. Теперь мы, наконец, готовы подключить полностью предварительно заполненный клиент к нашему приложению. Внутри _app.tsx
добавьте следующий код.
import type { AppProps } from 'next/app';
import { ApolloProvider } from '@apollo/client';
import useApollo from '../hooks/useApollo';
import '../styles/globals.css';
function MyApp({ Component, pageProps }: AppProps) {
const client = useApollo(pageProps);
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
}
export default MyApp;
Теперь вместо того, чтобы создавать здесь экземпляр клиента, мы используем хуки useApollo.ts
, чтобы получить клиента и передать его в ApolloProvider
.
С этим изменением наш Next.js приложение теперь полностью подключено к нашему экземпляру клиента Apollo и может использовать запросы GraphQL для извлечения и отображения данных.
Тестирование
Теперь давайте заполним запрос стран через getStaticProps
.
import Head from 'next/head';
import { useQuery } from '@apollo/client';
import type { GetStaticProps } from 'next';
import QUERY_COUNTRIES from './queryCountries.graphql';
import { addApolloState, initializeApollo } from '../apollo';
import styles from '../styles/Home.module.css';
export default function Home() {
// ...
}
export const getStaticProps: GetStaticProps = async (ctx) => {
const client = initializeApollo();
await client.query({
query: QUERY_COUNTRIES
});
return addApolloState(client, {
props: {},
});
};
Итак, здесь мы инициализируем клиента, делаем запрос и передаем клиент с заполненным кешем функции addApolloState
вместе с реквизитами страницы. Мы знаем, что эта функция добавит кеш apollo в pageProps
. Это гарантирует, что данные предварительно заполняются в кэше при отображении страницы на стороне клиента.
Теперь, если вы проверите вкладку сети, вы не увидите никакой загрузки и запросов, сделанных хуком useQuery
на клиенте, так как данные уже доступны в кеше.
В этом примере мы используем функцию getStaticProps
для создания статической страницы, которая включает функции SSG/ISR. Этот подход также можно использовать в функции getServerSideProps
, чтобы предоставить нам возможности SSR.
Теперь мы интегрировали возможности Next.js с Apollo Client, чтобы вывести наше приложение на новый уровень.
Вы можете найти полный код здесь