Четыре способа получения данных из GitHub GraphQL API в Next.js

В Next.js существует несколько способов отображения полученных данных. Одна из замечательных особенностей Next.js заключается в том, что мы можем смешивать и сочетать методы рендеринга в нашем приложении. Используя этот гибридный подход, мы получаем большую гибкость. В этой статье я расскажу о четырех способах визуализации данных, полученных из GitHub GraphQL API, в приложении Next.js. Вы узнаете о преимуществах и недостатках каждого варианта и будете знать, когда следует использовать каждый из них при написании собственного приложения.

Доступен репозиторий GitHub, а также живая демонстрация для ознакомления.

Что такое Next.js и почему я должен его использовать?

React — это библиотека JavaScript с открытым исходным кодом, разработанная компанией Facebook и предназначенная для создания интерактивных пользовательских интерфейсов. Именно с этой целью React стал самым распространенным и популярным выбором в мире JavaScript.

Next.js — это фреймворк React для создания производительных веб-приложений. Next.js сэкономит вам много времени и предоставит возможности и оптимизацию, с которыми трудно конкурировать. Он создан с учетом производительности и удобства для разработчиков. Из коробки мы получаем такие возможности, как расширенная оптимизация изображений, маршрутизация, функциональность бэкенда, интернационализация, встроенная поддержка CSS и многое другое.

В 2022 году это лучший и самый простой способ начать работу с приложением React.

Что такое опции рендеринга в Next.js?

Опция рендеринга определяет, когда генерируется HTML страницы. Мы можем предварительно рендерить страницы или рендерить их локально в браузере.

В Next.js у нас есть следующие варианты рендеринга:

  • рендеринг на стороне клиента
  • Рендеринг на стороне сервера
  • Статическая генерация сайта
  • Инкрементная статическая регенерация

Давайте рассмотрим, как работает каждый из этих способов.

Рендеринг на стороне клиента

Если вы знакомы с React, то, скорее всего, вы уже использовали хук useEffect для получения данных. Поскольку Next.js является фреймворком React, все, что мы обычно можем делать в React, мы можем делать и в Next.js.

import React, { useState, useEffect } from "react";

function App() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await fetch("/api/users");
      const data = await response.json();
      setUsers(data);
    };
    fetchUsers();
  }, [setUsers]);

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default App;
Вход в полноэкранный режим Выход из полноэкранного режима

Когда этот компонент монтируется, мы получаем данные из конечной точки /api/users и выполняем рендеринг. Эта выборка и рендеринг выполняются клиентом, поэтому мы называем это рендерингом на стороне клиента.

Рендеринг на стороне клиента — это предпочтительный метод, когда приоритетом является время отклика при взаимодействии. Динамические компоненты, отрисованные на стороне клиента, будут отображаться для пользователя как пустая область или пустой экран, пока не будут получены данные.

К счастью для нас, по крайней мере, части страницы могут быть отправлены статически, пока эти компоненты получают данные в Next.js. Мы можем улучшить впечатления, сообщив пользователю, что данные загружаются, а также обработать любые ошибки.

import React, { useState, useEffect } from "react";

function App() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [hasError, setHasError] = useState(false);

  useEffect(() => {
    const fetchUsers = async () => {
      setIsLoading(true);
      setHasError(false);
      try {
        const response = await fetch("/api/users");
        const data = await response.json();
        setUsers(data);
      } catch (error) {
        setHasError(true);
      }
      setIsLoading(false);
    };
    fetchUsers();
  }, [setUsers]);

  return (
    <>
      {hasError && <p>Oops! Something went wrong :(</p>}
      {isLoading ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      )}
    </>
  );
}

export default App;
Вход в полноэкранный режим Выход из полноэкранного режима

Еще проще было бы дать им вращающийся круг. Это немного более визуально привлекательно, чем текст Loading.... Вы можете написать свой собственный или воспользоваться таким проектом, как React Spinners.

Однако у рендеринга на стороне клиента есть несколько минусов. По мере увеличения размера пакета JavaScript ключевые показатели производительности, такие как First Paint (FP), First Contentful Paint (FCP) и Time to Interactive (TTI), страдают все больше и больше. Другими словами, наше приложение становится медленнее, а нагрузка ложится на клиента.

Кроме того, с рендерингом на стороне клиента вы не добьетесь хорошей видимости в поисковых системах. Этот вопрос может стать настоящей проблемой, если у вас, например, есть магазин электронной коммерции, и вы хотите, чтобы ваши товары индексировались поисковыми системами. То же самое можно сказать и о сообщениях в блогах. Но даже в этом случае это может быть ненужным и нежелательным соображением, например, в случае приборной панели зарегистрированного пользователя.

Рендеринг на стороне сервера

Рендеринг на стороне сервера генерирует страницы при каждом запросе. Другими словами, пользователь вводит URL-адрес в браузере, нажимает кнопку send, сервер получает запрос, обрабатывает страницу и выдает браузеру пользователя свежую, предварительно отрендеренную страницу.

В Next.js мы можем воспользоваться преимуществами рендеринга на стороне сервера с помощью getServerSideProps. Обратите внимание, что этот метод работает только на уровне страницы, в отличие от рендеринга на стороне клиента, который можно использовать в страницах или компонентах.

function Page({ data }) {
  // Render data...
}

// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`);
  const data = await res.json();

  // Pass data to the page via props
  return { props: { data } };
}

export default Page;
Вход в полноэкранный режим Выход из полноэкранного режима

Бремя получения и рендеринга возлагается на сервер. Вышеупомянутые показатели производительности, First Paint (FP), First Contentful Paint (FCP) и Time to Interactive (TTI), улучшатся. Этот прирост производительности растет по мере увеличения объема данных и количества JavaScript.

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

Но, как и во всем, есть компромисс. Время до первого байта (TTFB) может пострадать. TTFB измеряет продолжительность времени между запросом страницы и моментом, когда первый байт данных доходит до пользователя. Я бы не хотел использовать рендеринг на стороне сервера без сети доставки контента (CDN), такой как Cloudflare, Fastly, Vercel и т. д. А в одной из следующих статей я расскажу об использовании директив кэширования HTTP, которые могут смягчить многие из этих недостатков.

Наконец, веб-краулеры смогут индексировать страницы с серверным рендерингом, как в старые добрые времена. Видимость для поисковых систем идеальна при рендеринге на стороне сервера, и это нужно иметь в виду, когда приходит время выбирать метод рендеринга.

Создание статических сайтов

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

Вы никогда не сможете превзойти скорость и надежность статических страниц. Они подготовлены и готовы к работе, их можно кэшировать в CDN для достижения наилучшей производительности. Все показатели производительности, включая TTFB, не сравнятся ни с одним другим методом. Видимость в поисковых системах также идеальна.

По этой причине я бы сделал его вариантом по умолчанию и использовал при любой возможности. Однако если данные часто меняются, то вам придется выбрать другой метод.

В Next.js мы используем статическую генерацию сайта с помощью getStaticProps:

// posts will be populated at build time by getStaticProps()
function Blog({ posts }) {
  return (
    <ul>
      {posts.map(post => (
        <li>{post.title}</li>
      ))}
    </ul>
  );
}

// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
  // Call an external API endpoint to get posts.
  // You can use any data fetching library
  const res = await fetch("https://.../posts");
  const posts = await res.json();

  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  };
}

export default Blog;
Вход в полноэкранный режим Выход из полноэкранного режима

Инкрементная статическая регенерация

Новинкой в этом направлении является инкрементная статическая регенерация. Допустим, у вас есть блог с тысячами записей или магазин электронной коммерции со 100 000 товаров, и мы используем SSG для повышения производительности и видимости в поисковых системах. Время сборки в некоторых случаях может занять несколько часов.

Такая ситуация непрактична, а поскольку серверы стоят денег, либо ваши, либо чужие, мы платим за вычисления и пропускную способность. Инкрементная статическая регенерация была разработана как решение этой проблемы.

С помощью инкрементальной статической регенерации вы можете выполнять предварительный рендеринг заданных страниц в фоновом режиме во время получения запросов. В Next.js, чтобы использовать инкрементную статическую регенерацию, добавьте параметр revalidate в getStaticProps:

function Blog({ posts }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch("https://.../posts");
  const posts = await res.json();

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  };
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
  const res = await fetch("https://.../posts");
  const posts = await res.json();

  // Get the paths we want to pre-render based on posts
  const paths = posts.map(post => ({
    params: { id: post.id },
  }));

  // We'll pre-render only these paths at build time.
  // { fallback: blocking } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: "blocking" };
}

export default Blog;
Вход в полноэкранный режим Выход из полноэкранного режима

Небольшое введение в GraphQL

Далее давайте поговорим о GraphQL. Что это такое? GraphQL — это язык запросов и серверная среда выполнения для интерфейсов прикладного программирования (API). С помощью GraphQL мы можем сделать запрос на нужные нам данные и получить именно их: ни больше, ни меньше.

Вы можете быть знакомы с традиционными REST API, в которых вы обращаетесь к конечной точке и получаете набор данных, который определяется тем, как запрограммирован API. Возможно, вам придется получать данные из нескольких конечных точек, чтобы получить все, что вам нужно в данный момент, а затем отбрасывать ненужные фрагменты данных.

С GraphQL этого делать не нужно. Это одна из самых привлекательных особенностей GraphQL.

Некоторые люди немного пугаются, начиная работать с GraphQL, потому что он кажется сложным. Но это просто спецификация, которая склеивает существующие сетевые технологии. Она довольно интуитивно понятна, как только вы попробуете поиграть.

Вам не нужны никакие специальные инструменты, чтобы делать запросы GraphQL.

Давайте посмотрим, насколько это просто, сделав запрос из командной строки:

curl --request POST 
  --header 'content-type: application/json' 
  --url 'https://flyby-gateway.herokuapp.com/' 
  --data '{"query":"query { locations { id, name } }"}'
Войти в полноэкранный режим Выйти из полноэкранного режима

Обратите внимание, что мы делаем запрос POST, так как нам нужно отправить наш запрос на сервер. Серверы GraphQL имеют одну конечную точку. В теле запроса мы сообщаем, какие данные нам нужны, и в ответ получаем именно их.

В данном случае мы получаем следующий JSON:

{"data":{"locations":[{"id":"loc-1","name":"The Living Ocean of New Lemuria"},{"id":"loc-2","name":"Vinci"},{"id":"loc-3","name":"Asteroid B-612"},{"id":"loc-4","name":"Krypton"},{"id":"loc-5","name":"Zenn-la"}]}
Войти в полноэкранный режим Выйти из полноэкранного режима

Как это выглядит в приложении React? Существует множество клиентов GraphQL, которые мы можем использовать: Apollo Client, Relay, urql и другие, но для начала мы также можем использовать что-то простое, например, Fetch API браузера:

import React, { useState, useEffect } from "react";

const url = `https://flyby-gateway.herokuapp.com/`;

const gql = `
  query {
    locations {
      id
      name
    }
  }
`;

function App() {
  const [locations, setLocations] = useState([]);

  useEffect(() => {
    const fetchLocations = async () => {
      const response = await fetch(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          query: gql,
        }),
      });
      const {
        data: { locations: data },
      } = await response.json();
      setLocations(data);
    };
    fetchLocations();
  }, [setLocations]);

  return (
    <ul>
      {locations.map(location => (
        <li key={location.id}>{location.name}</li>
      ))}
    </ul>
  );
}

export default App;
Войти в полноэкранный режим Выход из полноэкранного режима

GitHub GraphQL API

Теперь давайте перейдем к рассмотрению GitHub GraphQL API. У GitHub есть REST API и GraphQL API. Здесь мы сосредоточимся на GraphQL API.

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

repo
read:packages
read:org
read:public_key
read:repo_hook
user
read:discussion
read:enterprise
read:gpg_key
Войти в полноэкранный режим Выйти из полноэкранного режима

API сообщит вам, если потребуется больше.

Давайте сделаем еще один запрос из командной строки с помощью curl:

curl -H "Authorization: bearer token" -X POST -d " 
 { 
   "query": "query { viewer { login }}" 
 } 
" https://api.github.com/graphql
Войти в полноэкранный режим Выйти из полноэкранного режима

Заменим token на только что сгенерированную строку токена.

В ответ мы получим что-то вроде:

{ "data": { "viewer": { "login": "jpreagan" } } }
Войти в полноэкранный режим Выход из полноэкранного режима

Эй, это я! Используя свой токен, вы увидите там и свое имя пользователя. Отлично! Теперь мы знаем, что это работает.

Имейте в виду, что мы хотим сохранить этот токен конфиденциальным и убедиться, что он не попадет в наше репо. Мы сохраним его в файле .env.local. Этот файл должен выглядеть примерно так:

GITHUB_TOKEN=mytoken
Вход в полноэкранный режим Выйти из полноэкранного режима

Где mytoken — это сгенерированная вами строка.

Теперь мы можем получить к ней доступ через process.env.GITHUB_TOKEN со встроенной поддержкой переменных среды в Next.js. Однако мы не сможем получить безопасный доступ к этим переменным, просто поместив их в заголовки вышеприведенных примеров. Нам нужно будет использовать getServerSideProps, getStaticProps или использовать API Routes, о которых я расскажу в ближайшее время.

Пока же давайте посмотрим на GitHub GraphQL Explorer. Это экземпляр GraphiQL, который является удобным инструментом для создания GraphQL-запросов в браузере.

Лучший способ познакомиться с ним — просто немного поиграть с ним. Вот запрос, который я придумал, чтобы понять, что мне может понадобиться:

query {
  viewer {
    login
    repositories(
      first: 20
      privacy: PUBLIC
      orderBy: { field: CREATED_AT, direction: DESC }
    ) {
      nodes {
        id
        name
        description
        url
        primaryLanguage {
          color
          id
          name
        }
        forkCount
        stargazerCount
      }
    }
  }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

По мере изменения требований к данным вы можете вернуться к проводнику GraphQL, обновить и протестировать эти запросы, которые вы можете скопировать и вставить обратно в свой код. Такой опыт, на мой взгляд, гораздо приятнее, чем продираться сквозь документацию REST API.

Рендеринг на стороне клиента

Теперь давайте вернемся к нашему примеру рендеринга на стороне клиента. Переделаем пример fetchUsers, приведенный выше, но сделаем несколько вещей по-другому.

Во-первых, как я уже говорил, мы не можем просто поместить токены доступа в заголовки нашего исходного кода. Это будет отправлено клиенту, и любой может просто открыть сетевую вкладку и прочитать ваши маркеры доступа, что сделает их открытыми и небезопасными.

Вместо этого мы можем поместить их в getServerSideProps или getStaticProps, и там они будут безопасны, но это будет для рендеринга на стороне сервера и генерации статических сайтов соответственно. Здесь мы воспользуемся еще одной замечательной функцией Next.js под названием API Routes.

Вкратце, мы можем сделать JavaScript или TypeScript файл в директории pages/api, который будет служить конечной точкой API. Они не будут передаваться клиенту и поэтому являются безопасным способом скрыть наши маркеры доступа и одним из единственных вариантов, который мы имеем для этого при рендеринге на стороне клиента.

(Другим вариантом может быть создание бессерверной функции на другом сервисе, например, функции AWS Lambda, но я не буду рассматривать это здесь. Зачем это делать, если у нас есть отличное решение, встроенное в Next.js).

Вот базовый пример: pages/api/hello.js:

export default function handler(req, res) {
  res.status(200).json({ message: 'Hello, World! })
}
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь, когда наш сервер разработки запущен, мы можем curl http://localhost:3000/hello, и нас поприветствуют:

{ "message": "Hello, World!" }
Войти в полноэкранный режим Выход из полноэкранного режима

Я нахожу это совершенно потрясающим! Все, что нам нужно сделать, это экспортировать обработчик запроса функции по умолчанию (называемый handler), который принимает два параметра: req и res. Это не Express, но вы заметите, что синтаксис похож на Express. Насколько это круто?

Итак, давайте напишем конечную точку с учетом наших целей рендеринга на стороне клиента:

// src/pages/github.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { GraphQLClient, gql } from "graphql-request";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const endpoint = "https://api.github.com/graphql";

  const client = new GraphQLClient(endpoint, {
    headers: {
      authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
    },
  });

  const query = gql`
    {
      viewer {
        login
        repositories(
          first: 20
          privacy: PUBLIC
          orderBy: { field: CREATED_AT, direction: DESC }
        ) {
          nodes {
            id
            name
            description
            url
            primaryLanguage {
              color
              id
              name
            }
            forkCount
            stargazerCount
          }
        }
      }
    }
  `;

  const {
    viewer: {
      repositories: { nodes: data },
    },
  } = await client.request(query);

  res.status(200).json(data);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Я уже упоминал, что при получении данных GraphQL мы можем использовать практически любой клиент. Graphql-request от Prisma — это простой и легкий вариант, и именно его я использовал здесь.

С этим кодом на месте мы можем протестировать нашу конечную точку с помощью curl http://localhost.com/api/github и теперь мы получим наши данные. Ура, теперь давайте напишем фронтенд-часть этого уравнения.

// src/pages/csr.tsx
import type { NextPage } from "next";
import type { Repository } from "../types";
import useSWR from "swr";
import Card from "../components/card";

interface ApiError extends Error {
  info: any;
  status: number;
}

const fetcher = async (url: string) => {
  const response = await fetch(url);

  if (!response.ok) {
    const error = new Error(
      "An error occurred while fetching the data"
    ) as ApiError;
    error.info = await response.json();
    error.status = response.status;
    throw error;
  }

  const data = await response.json();

  return data;
};

const Csr: NextPage = () => {
  const { data, error } = useSWR<Repository[], ApiError>(
    "/api/github",
    fetcher
  );

  if (error) return <div>Something went wrong :(</div>;
  if (!data) return <div>Loading...</div>;

  return (
    <>
      {data.map(
        ({
          id,
          url,
          name,
          description,
          primaryLanguage,
          stargazerCount,
          forkCount,
        }) => (
          <Card
            key={id}
            url={url}
            name={name}
            description={description}
            primaryLanguage={primaryLanguage}
            stargazerCount={stargazerCount}
            forkCount={forkCount}
          />
        )
      )}
    </>
  );
};

export default Csr;
Вход в полноэкранный режим Выход из полноэкранного режима
// src/components/card.tsx
import type { Repository } from "../types";

const Card = ({
  url,
  name,
  description,
  primaryLanguage,
  stargazerCount,
  forkCount,
}: Repository) => {
  return (
    <>
      <article>
        <h2>
          <a href={url}>{name}</a>
        </h2>
        <p>{description}</p>
        <p>
          {primaryLanguage && (
            <span style={{ backgroundColor: primaryLanguage?.color }}>
              {primaryLanguage?.name}
            </span>
          )}
          {stargazerCount > 0 && (
            <a href={`${url}/stargazers`}>{stargazerCount}</a>
          )}
          {forkCount > 0 && <a href={`${url}/network/members`}>{forkCount}</a>}
        </p>
      </article>
    </>
  );
};

export default Card;
Войти в полноэкранный режим Выход из полноэкранного режима

Здесь мы используем SWR для выборки. Это инструмент компании Vercel, основанный на директиве кэширования HTTP stale-while-revalidate, ставшей популярной в RFC 5861. SWR возвращает кэшированные данные (stale), затем посылает запрос на выборку (revalidate) и, наконец, приходит с обновленными данными.

Он быстрый, легкий, обрабатывает кэширование, и мы можем использовать его с любым протоколом. Мы можем использовать этот хук, задав ему конечную точку и функцию fetcher, которую мы определили выше.

Давайте проверим время до первого байта (TTFB) этого развернутого кода:

curl --output /dev/null 
     --header 'Cache-Control: no-cache' 
     --silent 
     --write-out "Connect: %{time_connect} TTFB: %{time_starttransfer} Total time: %{time_total} n" 
     https://github-graphql-nextjs-example.vercel.app/csr
Вход в полноэкранный режим Выйти из полноэкранного режима

Мы получаем результаты:

Connect: 0.082094 TTFB: 0.249804 Total time: 0.250051
Вход в полноэкранный режим Выход из полноэкранного режима

Неплохо! Имейте в виду несколько вещей: (а) я живу на сельском острове посреди Тихого океана (цифры фантастические для моего местоположения), (б) кэширование отключено, и (в) это время до первого байта, но мы получаем Loading..., пока данные не будут действительно получены; затем клиент должен повторить рендеринг.

Рендеринг на стороне сервера

Как это выглядит при использовании рендеринга на стороне сервера? Мы будем использовать getServerSideProps. Давайте проверим, как это выглядит.

import type { Repository } from "../types";
import { GraphQLClient, gql } from "graphql-request";
import Card from "../components/card";

type SsrProps = {
  data: Repository[];
};

const Ssr = ({ data }: SsrProps) => {
  return (
    <>
      {data.map(
        ({
          id,
          url,
          name,
          description,
          primaryLanguage,
          stargazerCount,
          forkCount,
        }) => (
          <Card
            key={id}
            url={url}
            name={name}
            description={description}
            primaryLanguage={primaryLanguage}
            stargazerCount={stargazerCount}
            forkCount={forkCount}
          />
        )
      )}
    </>
  );
};

export async function getServerSideProps() {
  const endpoint = "https://api.github.com/graphql";

  const client = new GraphQLClient(endpoint, {
    headers: {
      authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
    },
  });

  const query = gql`
    {
      viewer {
        login
        repositories(
          first: 20
          privacy: PUBLIC
          orderBy: { field: CREATED_AT, direction: DESC }
        ) {
          nodes {
            id
            name
            description
            url
            primaryLanguage {
              color
              id
              name
            }
            forkCount
            stargazerCount
          }
        }
      }
    }
  `;

  const {
    viewer: {
      repositories: { nodes: data },
    },
  } = await client.request(query);

  return { props: { data } };
}

export default Ssr;
Вход в полноэкранный режим Выход из полноэкранного режима

Это работает так же, как мы делали в нашем рендеринге на стороне клиента выше с API Routes, но вместо этого в этот раз мы используем getServerSideProps. Токен доступа будет в безопасности, так как он доступен только бэкенду и никогда не отправляется клиенту.

Для собственного спокойствия вы можете использовать инструмент Next.js Code Elimination, чтобы проверить, что именно отправляется клиенту.

Давайте проверим, что сейчас происходит с первым байтом:

curl --output /dev/null 
     --header 'Cache-Control: no-cache' 
     --silent 
     --write-out "Connect: %{time_connect} TTFB: %{time_starttransfer} Total time: %{time_total} n" 
     https://github-graphql-nextjs-example.vercel.app/ssr
Вход в полноэкранный режим Выход из полноэкранного режима
Connect: 0.074334 TTFB: 0.504285 Total time: 0.505289
Войти в полноэкранный режим Выход из полноэкранного режима

Хорошо, TTFB теперь увеличилось, но опять же, имейте в виду все эти вещи: (а) страница отправляется клиенту предварительно отрендеренной, нет никакого Loading..., и (б) это без кэширования, которое потенциально могло бы ускорить все на много.

Данные также являются свежими на момент запроса! Однако пользователю придется нажать кнопку обновления в браузере, если данные изменятся.

Генерация статических сайтов

Давайте теперь рассмотрим генерацию статических сайтов.

Мы внесем лишь одно небольшое изменение в код рендеринга на стороне сервера: вместо getStaticProps мы будем использовать getServerSideProps:

​​/* ... */
const Ssg = ({ data }: SsgProps) => {
  return (/* ... */);
};

export async function getStaticProps() {
  /* ... */
}

export default Ssg;
Вход в полноэкранный режим Выход из полноэкранного режима

Вот и все! Теперь наша страница будет предварительно отрендерена во время сборки. Как выглядит время до первого байта?

curl --output /dev/null 
     --header 'Cache-Control: no-cache' 
     --silent 
     --write-out "Connect: %{time_connect} TTFB: %{time_starttransfer} Total time: %{time_total} n" 
     https://github-graphql-nextjs-example.vercel.app/ssg
Вход в полноэкранный режим Выход из полноэкранного режима
Connect: 0.073691 TTFB: 0.248793 Total time: 0.250743
Вход в полноэкранный режим Выход из полноэкранного режима

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

В чем недостаток? Ну, данные получаются во время сборки. Поэтому, если данные обновляются после сборки, мы будем предоставлять устаревшие данные, но следующий вариант может помочь в этом.

Инкрементная статическая регенерация

Наконец, давайте рассмотрим инкрементную статическую регенерацию. Мы можем взять точно такой же код из нашей статической генерации сайта и добавить реквизит revalidate.

​​/* ... */

const Isr = ({ data }: IsrProps) => {
  return (/* ... */);
};

export async function getStaticProps() {
  /* ... */
  return {
    props: {
      data,
    },
    revalidate: 5,
  };
}

export default Isr;
Вход в полноэкранный режим Выход из полноэкранного режима

Реквизит revalidate — это измерение времени в секундах, которое дает серверу знать, как долго данные будут считаться устаревшими. Во время сборки у нас будет предварительно отрендеренная страница, как обычно при статической генерации сайта, и когда пользователь запросит новую страницу, мы дадим ему ее и проверим на устарелость. Если страница устарела, то делаем ревалидацию: создается новая копия.

Как здорово! Теперь мы можем получить лучшее из обоих миров.

Время до первого байта, как и ожидалось, на одном уровне со статической генерацией сайта:

curl --output /dev/null 
     --header 'Cache-Control: no-cache' 
     --silent 
     --write-out "Connect: %{time_connect} TTFB: %{time_starttransfer} Total time: %{time_total} n" 
     https://github-graphql-nextjs-example.vercel.app/isr
Вход в полноэкранный режим Выход из полноэкранного режима
Connect: 0.076293 TTFB: 0.255100 Total time: 0.255657
Вход в полноэкранный режим Выход из полноэкранного режима

Подведение итогов

Это четыре способа отображения полученных данных в Next.js. Вы можете проверить репозиторий GitHub, клонировать его, использовать свой токен доступа и попробовать его в тестовом режиме. Или посмотрите живую демонстрацию.

Оставьте звезду на репозитории, если вы нашли его полезным! Как всегда, обращайтесь ко мне в Twitter, если я могу быть вам полезен.

Оцените статью
devanswers.ru
Добавить комментарий