В этом посте рассказывается, как мы можем создать приложение React / NextJS с Redux, которое достигает 100% результатов аудита с серверным рендерингом, поддержкой локализации и может быть установлено как PWA и перемещаться в автономном режиме.

next.js

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

Разработка приложения React будет довольно привычным делом, вам нужно будет отключить реагирующий маршрутизатор со встроенным маршрутизатором и знать, что ваши компоненты должны быть исполняемыми в NodeJS (точно так же, как если бы вы их модульное тестирование).

Основное отличие - это немного волшебства, которое мы можем добавить на наши страницы:

// Calls before the page is mounted, the call will happen on the server if it’s the first page we visit
static async getInitialProps({ ctx: { store } }) {
await store.dispatch(AppActions.getWidgets());
return {};
}

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

Вместо того, чтобы изрыгать всю мощь следующего, я бы рекомендовал просто пройти через их руководство по началу работы. В этом посте подробно рассказывается, как я добавил redux, sagas и добился 100% -ного результата на Lighthouse.

Мне скучно, просто пришлите мне код.

"Отлично". Проект также размещен на https://nextjs-redux.kyle-ssg.now.sh/. Но читайте дальше, если вам интересно.

next.js с Redux

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

import App, { Container } from ‘next/app’;
import Head from ‘next/head’;
import React from ‘react’;
import { Provider } from ‘react-redux’;
import createStore from ‘../common/store’;
import withRedux from ‘next-redux-wrapper’;
class MyApp extends App {
  static async getInitialProps({ Component, ctx }) {
     let pageProps;
     // Ensure getInitialProps gets called on our child pages
     if (Component.getInitialProps) {
       pageProps = await Component.getInitialProps({ ctx });
     }
     return { pageProps };
 }
...
render() {
   const { Component, pageProps, store } = this.props;
   return (
     <Container>
     <Provider store={store}>
     <>
       <Head>
         {/*…script and meta tags*/}
         <title>TheProject</title>
       </Head>
       <Header/>
        <Component {…pageProps} />
      </>
      </Provider>
      </Container>
 );
 }
}
export default withRedux(createStore)(MyApp);

Некоторые из них, вероятно, будут вам знакомы, но основные отличия заключаются в следующем:

  • В нашем приложении маршрута мы должны убедиться, что функции getInitialProps наших страниц вызываются перед рендерингом.
  • Next.js предоставляет компонент Head, который позволяет нам отображать любые стандартные теги, которые находятся внутри заголовка, это можно делать даже на каждой странице. Это полезно для добавления открытых графиков / метатегов / заголовков на страницу.
  • next-redux-wrapper - это готовая к использованию библиотека, которая позволяет нам использовать createStore.

Результат

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

Это происходит потому, что getInitialProps вызывается на сервере во время начальной загрузки страницы, он знает, на какой странице его вызывать, в зависимости от маршрута.

2. Получение 100% баллов Lighthouse

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

Исходный балл

Базовая оценка была неплохой, при этом производительность не была проблемой для страницы с избыточным кодом, обращающейся к API.

Доступность

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

Соответствующий цветовой контраст

Lighthouse достаточно умен, чтобы знать, какие из ваших элементов не соответствуют пороговым значениям коэффициента контрастности WCAG 2 AA, заявляя, что ваш передний план и фон должны иметь коэффициент контрастности не менее 4,5: 1 для мелкого текста или 3: 1 для большого текста. Вы можете запускать такие инструменты, как Проверка контраста Web AIM. Быстрое изменение CSS исправило это, но, очевидно, это будет означать хороший рефакторинг для сайтов с богатым контентом.

Локализация

Этот был немного сложнее. Чтобы сделать это хорошо, я хотел, чтобы рендеринг на стороне сервера определял предпочтительный языковой стандарт пользователя и устанавливал атрибут lang, а также обслуживал локализованный контент. Во время поиска я наткнулся на next-i18next, однако заметил, что он не поддерживает бессерверную версию, и с ней трудно делиться строками локали с помощью react-native-localization.

Я хотел что-то, что работало бы с реакцией-локализацией, поэтому мой подход был следующим:

  • Когда документ пытается отобразить на сервере, мы хотим получить предпочтительный языковой стандарт и установить атрибут lang в тег HTML. Эта информация поступает с сервера либо из файла cookie, который мы могли установить, либо путем анализа Accept-Language Header. Фрагмент кода, показывающий, как я это сделал, можно найти здесь.
// _document.js
 static async getInitialProps(ctx) {
   const initialProps = await Document.getInitialProps(ctx);
   const locale = API.getStoredLocale(ctx.req);
   return { …initialProps, locale };
 }
 …
 render() {
   return (
     <html lang={this.props.locale}>
      …
     </html>
   )
 }
  • 2: я определяю несколько локализованных строк
// localization.js
import LocalizedStrings from ‘react-localization’; 
const Strings = new LocalizedStrings({
  en: {
   title: ‘Hello EN’,
   colour: ‘colour’,
  },
  ‘en-US’: {
   title: ‘Hello US’,
  },
});
export default Strings;
  • 3. Я хочу, чтобы мое приложение знало, какой язык находится в магазине, чтобы я мог использовать эту информацию позже.
// _app.js
 static async getInitialProps({ Component, ctx }) {
   let pageProps;
    // Retrieve the locale from cookie or headers   
    const locale =      API.getStoredLocale(ctx.req); 
   // Post startup action with token and locale   
   await ctx.store.dispatch(AppActions.startup({ locale }));
 …
 }
  • 4: Я устанавливаю язык один раз в своем приложении на начальном рендере клиента ** и ** сервера.
// _app.js
render(){
   if (!initialRender) {
     initialRender = true;
     const locale = store.getState().locale;
     if (locale) {
       Strings.setLanguage(locale);
     }
   }
 …
}
  • 5. На своих страницах я теперь могу свободно использовать локализованные строки.
// pages/index.js
 render() {
   return (
     <div className=”container”>
       <h1>Home</h1>
       {Strings.title}
     </div>
   );
 }

Лучшие практики

Поскольку у проекта были довольно современные библиотеки и не было ничего непослушного, у него уже был хороший результат. Единственное, что нам нужно было сделать, это использовать http2 и SSL, что больше зависит от того, как вы размещаете приложение. Использование Zeit покрыло оба этих вопроса.

SEO

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

Прогрессивное веб-приложение

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

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

/static/manifest.json
{
   “short_name”: “Project Name”,
   “name”: “Project Name”,
   “icons”: [
     {
       “src”: “/static/images/icons-192.png”,
       “type”: “image/png”,
       “sizes”: “192x192”
     },
     {
       “src”: “/static/images/icons-512.png”,
       “type”: “image/png”,
       “sizes”: “512x512”
     }
    ],
    “start_url”: “/?source=pwa”,
    “background_color”: “#3367D6”,
    “display”: “standalone”,
    “scope”: “/”,
    “theme_color”: “#3367D6”
}
//_app.js
<link rel=”manifest” href=”/static/manifest.json”/>

Автономная поддержка обслуживающего персонала

Благодаря next-offline добавить поддержку сервис-воркера оказалось просто. Однако заставить сервис-воркера работать без сервера и размещаться на Zeit было немного сложно, нам пришлось добавить маршрут для нашего сервера, чтобы обслуживать правильный заголовок контента.

// now.json
{
 “version”: 2,
 “routes”: [
 {
 “src”: “^/service-worker.js$”,
 “dest”: “/_next/static/service-worker.js”,
 “headers”: {
 “Service-Worker-Allowed”: “/”
 }
 }
 …
 ]
}

А затем настройте next-offline для обслуживания сервис-воркера из

//next.config.js
{
 target: ‘serverless’,
 // next-offline options
 workboxOpts: {
 swDest: ‘static/service-worker.js’,
 ...

Результат

В результате у нас теперь есть надежный базовый проект со 100% оценкой аудита, который можно установить и использовать в автономном режиме. Не стесняйтесь Клонировать и взламывать!