Once upon a time Несколько лет назад, когда я только начинал работать с вебом на Java, мы работали с JSP. Вся страница генерировалась на сервере и отправлялась клиенту. Но потом встал вопрос о том, что ответ приходит слишком долго…
Мы начали использовать подход, при котором отдается пустой темплейт страницы, а все данные уже постепенно подгружались Аяксом. Все были счастливы, странички показывались. Пока мы не поняли, что наделали себе за шиворот, так как CSR отрицательно сказывается на поисковой оптимизации и производительности на мобильных устройствах. Но потом я снова услышал про поддержку SSR JS-фреймворками.
И что же получается, история повторяется?
Какие есть принципы работы SSR?
1. Prerendering. В простейшем случае генерируется N HTML-файлов, которые кладутся на сервер и возвращаются как есть — то есть возвращается статика, во время запроса мы ничего не генерируем.
2. Как и в случае с JSP, на сервере генерируется полный HTML со всем контентом и возвращается клиенту. Но, чтобы не генерировать страницу на каждый запрос (коих может быть миллион и наш сервер загнется), давайте добавим кэш прокси. Например, варниш.
Когда может быть применим каждый из этих способов:
1. Когда имеет смысл генерировать пачку HTML-файлов? Очевидно, в том случае, когда данные на сайте меняются чуть реже чем никогда. Например, корпоративный сайт ларька по ремонту обуви, что за углом (да-да, тот дяденька, который меняет набойки в ларьке 2х2 метра, тоже захотел сайт фирмы — и, конечно же, со страницей миссии компании). Для такого сайта вообще не надо заморачиваться на предмет фреймворков, SSR и прочих свистелок, но это сферический пример. Что делать, если у нас блог, в котором 1к постов? Иногда мы их актуализируем, иногда добавляем новые. Сгенерировать 1к+ статичных файлов… Что-то не то. А если мы изменяем пост, то надо перегенерировать определенный файлик. Хм…
2. И вот тут нам подходит второй способ. Где мы генерируем первый раз на лету, а потом кэшируем ответ в проксирующем сервисе. Время кэширования может быть час/два/день — как угодно. Если у нас 10 000 заходов в час на пост (невероятно, правда?), то только первый запрос дойдет до сервера. Остальные получат в ответ кэшированную копию, и наш сервер с большей вероятностью будет жить. В случае обновления какого-то поста нам просто нужно сбросить закэшированную запись, чтобы по следующему реквесту сгенерировалась уже актуальная страница.
От слов к делу:
Hello world repo.
0) generate hello world
Для быстрого старта сообщество Nuxt подготовило базовые темплейты, установить любой из них можно командой:
$ vue init <template-name> <project-name>
По умолчанию предлагается started-template, его и возьмем для нашего примера. Хотя в реальном приложении мы выбрали express-template. Назовем проект незамысловато:
$ vue init nuxt-community/starter-template habr-nuxt-example
$ cd habr-nuxt-example
$ yarn # или npm install, как будет угодно
$ yarn dev
Вжух, мы сгенерировали hello world. Перейдя по урлу, можно увидеть сгенерированную страницу:
1) Webpack и Linting
Nuxt из коробки имеет настроенные вебпак с поддержкой ES6 (babel-loader), Vue однофайловые компоненты (vue-loader), а также SCSS, JSX и прочее.
Если этих возможностей недостаточно, конфиг вебпака можно расширить. Идем в nuxt.config.js, и в build.extend мы имеем возможность модифицировать конфиг.
Для примера добавим линтинг стилей по аналогии с линтингом кода — важный, на наш взгляд, пункт, для поддержания единообразной кодовой базы. Это хорошая привычка, которая поможет избежать многих подводных камней.
Пример расширения конфига (подключение конфиг-файла для дева на основе переменной окружения):
config.plugins.push(
new StylelintPlugin({
files: [
'**/*.vue',
'assets/scss/**/*.scss'
],
configFile: './.stylelintrc.dev.js'
})
)
Остальные изменения можно посмотреть в репо по тегу, эти изменения помогут нам держать стили в порядке.
И пример конфиг-файла линтера: используем Standard JS, как общепринятое в Vue/Nuxt решение:
...
extends: [
- 'plugin:vue/essential'
+ 'standard',
+ 'plugin:vue/recommended'
],
…
2) Для примера работы с данными будем использовать вот это API:
Подключим Axios как плагин, создаем новый файл в директории plugins:
import * as axios from 'axios'
let options = {}
// The server-side needs a full url to works
if (process.server) {
options.baseURL = `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}`
}
export default axios.create(options)
И пример использования:
import axios from '~/plugins/axios'
export default {
async asyncData ({ params }) {
const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts')
return { data }
}
}
Остальное в репе по тегу.
Цифры загрузки:
1) SSR + Varnish
Первый запрос:
Второй:
2) No-ssr
Второй реквест с фастли
Пустая страница пришла быстро, но потребовалось 2 секунды на то, чтобы сгенерировать на ней контент.
Conclusion
Что в итоге? Мы разобрались, как получить минимально сконфигурированное запускаемое SSR-приложение. Добавили Linting для сохранения стиля кода с самого начала жизни проекта, а также обозначили общую архитектуру. Можно писать свой гугол.
Автор: UITcom