Рендеринг на стороне сервера (я буду использовать SSR позже для краткости) - это довольно недавний термин, он начал свою жизнь всего пару лет назад. Изначально основной проблемой было отсутствие SEO для сложных одностраничных приложений, и появились проекты типа prerenderer. Основная идея заключалась в предварительном рендеринге приложения в другом месте (например, PhantomJS) с ожиданием выполнения javascript, а затем просто захватом всего html и последующим его обслуживанием для роботов. При таком подходе вы обновляли свои страницы только иногда (возможно, в фоновом режиме), и они обслуживались только для поисковых систем, поэтому на прямых пользователей это никак не повлияло.

После того, как появился response, с одним из принципов, согласно которому все, что отрисовывается внутри него, должно быть доступно для обработки на стороне сервера (или на любой другой стороне на самом деле, позже было разрешено писать рендеры, ориентированные на реагирование и реагирующие iot), он стал Понятно, что можно отобразить весь контент на node.js, а затем просто передать его пользователю. Позже эту идею выбрали другие фреймворки (например, Vue.js имеет довольно похожие концепции рендеринга), поэтому в настоящее время SSR считается хорошей практикой, а не ракетостроением (как это было 2 года назад).

Зачем нам это нужно?

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

Что ж, краулеры на самом деле не так хороши - и это гугл! Может быть, другие движки еще хуже, поэтому лучше оставаться в безопасности и предварительно рендерить весь свой контент. Кроме того, парсинг javascript на мобильных устройствах довольно медленный, поэтому, если делегировать всю работу по отправке javascript, затем синтаксическому анализу, затем выполнению, а затем, после нескольких возможных запросов AJAX (которые могут дать сбой в небрежной сети 3G), мы отрендерим контент на мобильном телефоне. Вместо этого мы можем сделать следующее:

  • делать все запросы на сервере (у которого идеальная пропускная способность)
  • выполнить начальный рендеринг на сервере
  • отправить простой html перед javascript
  • отправить javascript (так пойдет полный цикл, который мы описали ранее)

Итак, пользователь сначала получит html (перед любым javascript приложения), и только после того, как мы начнем разбирать и выполнять javascript. Приложение не будет отвечать до этого момента, и это очевидный недостаток (который можно в некоторой степени обойти, запомнив все взаимодействия пользователя), но, тем не менее, возможность быстро увидеть важный контент бесценна. Для десктопов это не так важно, потому что процессоры там обычно намного быстрее, но это приятное дополнение.

Нужен ли вам рендеринг на стороне сервера? Если вам нужна SEO, то это определенно большой бонус. Но если вам нужно только SEO, то обратите внимание на webpack-prerenderer-plugin. Если производительность и критический путь рендеринга имеют решающее значение, я бы рекомендовал взглянуть на количество пользователей с мобильных устройств и попытаться проанализировать, из каких сетей ваши пользователи используют ваше приложение, и являются ли они устройствами низкого уровня с возможным использование мобильных сетей, то я определенно рекомендую вам попробовать. Кроме того, один из лучших сценариев тестирования - это действительно получить настоящий телефон Android и какое-то время использовать свое приложение - вы откроете для себя некоторые интересные вещи, о которых вы никогда раньше не думали!

Решения

Как я уже упоминал ранее, эта область до сих пор не нанесена на карту, поэтому реальной передовой практики нет. Шаблонные панели, если они обеспечивают какой-либо рендеринг на стороне сервера, обычно доходят до простого рендеринга вашего приложения без какой-либо предварительной выборки (и без предложения каких-либо способов сделать это). Итак, вы в основном сами по себе после того, как перейдете в часть кода сервера, которая вызывает что-то вроде renderToString. Исходя из этого, я предполагаю, что мы говорим о React и response-router - хотя этого можно достичь, скажем, vue, я больше знаком с React, и подходы будут такими же. (как вы знаете, любая хорошая идея во внешнем мире будет немедленно скопирована, как это произошло с виртуальной DOM).

Присоединение функции к компонентам верхнего уровня

Итак, в момент рендеринга, когда мы действительно вызываем renderToString, мы знаем, какой компонент собираемся рендерить, и это подводит нас к первому предположению. Если мы знаем компонент, почему мы не можем просто прикрепить функцию для разрешения всех необходимых данных? Благодаря маршрутизатору мы точно знаем, какие маршруты мы собираемся получить, поэтому мы можем добавить к нему необходимые функции запросов. Более того, это официальная рекомендация как для react-router, так и для vue.

Последовательность следующая:

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

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

Я действительно не хочу предоставлять какой-либо код, потому что response-router немного изменил свой API, но многие проекты все еще используют более старую версию (например), но идея должна быть ясной.

Проблемы с этим подходом

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

Кроме того, вы по существу дублируете свой код - вам нужно будет предварительно загрузить его внутри других компонентов (но на клиенте, скорее всего, вы получите их внутри вложенных компонентов), и из-за этого многофайлового характера его действительно легко сделать их рассинхронизированными (совершенно случайно!).

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

Двойной рендеринг

Когда мы визуализируем наше реагирующее приложение на сервере, вызывается одна ловушка жизненного цикла componentWillMount (или мы можем использовать constructor, важно то, что она вызывается как на клиенте, так и на сервере).

Обратите внимание: поскольку componentWillMount вызывается на сервере, все запросы, которые вы запускаете на нем, будут выполняться, поэтому даже если вы ничего не ждете, они все равно будут выполняться. Так что, как правило, такие запросы лучше помещать в componentDidMount.

Идея заключается в следующем: если мы сначала визуализируем наше приложение, не сохраняя визуализированный вывод, все componentWillMount хуки будут выполнены, и если мы каким-то образом поймаем их все и подождем, после разрешения мы сможем безопасно визуализировать приложение снова, но теперь он будет заполнен данными. Подход намного проще, потому что мы просто выполняем приложение несколько раз. Он почти решает обе проблемы предыдущего подхода - нет никакого связывания, а также тот же componentWillMount будет вызываться на стороне клиента - так что наш контент

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

Реализация двойного рендеринга

Я более опытен в этом подходе, и его можно обобщить намного лучше, чем предыдущий. В моей библиотеке redux-tile я предлагаю именно такой подход для предварительной выборки для рендеринга на стороне сервера. Основная идея заключается в том, что мы хотели бы перехватить все асинхронные запросы, а затем дождаться их всех и только после этого отобразить окончательный результат. Мы можем создать некоторый объект для хранения запросов, а затем передать его всем асинхронным действиям, но это довольно раздражает, а также требует, чтобы мы переписали всю часть уровня данных. В приложении react + redux у нас есть два места, где мы можем создавать экземпляры независимых объектов для каждого запроса: context и middleware. Первый подход потребует от нас передать этот объект действиям (а синтаксис для получения контекста не самый удобный), поэтому мы остаемся только с одной возможностью - промежуточным программным обеспечением. Промежуточное ПО в redux позволяет нам обрабатывать различные типы отправленных действий - в redux-tile промежуточное ПО обрабатывает возвращаемые функции. Он передает объект с обещаниями, поэтому асинхронные плитки (думайте о них как о модулях redux) сохраняют в нем свои запросы (и удаляют их после разрешения), и это позволяет нам захватить все активные обещания, а затем ждать их.

Заключение

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

Первоначально опубликовано на сайте bloomca-me.github.io 11 июня 2017 г.