Всем привет. Я являюсь ведущим frontend-разработчиком компании 21Yard. Мы разрабатываем сервис для поиска строительных подрядчиков.
На проект я пришел желторотым масленком, который мало смыслил в seo-продвижении продукта, но жизнь внесла свои коррективы, и сейчас я хочу рассказать о современном ssr-фреймворке -vike, показать его основные аспекты.
P.S. Статья рассчитана в первую очередь на таких же молодых и зеленых, но будет возможно полезна и матерым калачам.
Мотивация для написания статьи
Vike - молодой фреймврорк, который еще не успел добраться до версии 1.0.0. Он имеет неплохую документацию на английском, но, к сожалению, русскоязычных гайдов на него нет. Во многих микро-моментах приходилось разбираться самому, т.к. даже спросить было не у кого. Надеюсь, эта статья откроет для многих альтернативу next-у, а кому-то поможет разобраться в основных положениях этого замечательного фреймворка.
Еще немного введения...
Придя на проект, я с энтузиазмом взялся за дело. На момент старта моей работы у нас уже существовал интернет-портал, написанный на php. К сожалению, он был написан на устаревшем фреймворке, поэтому было принято решение переписать его с нуля на чем-то современном - выбор пал на React. Однако, параллельно кодингу шел и маркетинг. К работе был привлечен seo-специалист, по указаниям которого мне нужно было вносить микро-правки в старый портал. Тогда я узнал, что такое seo, и что для него нужен ssr...
Справка для самых маленьких
Обычное React-приложение, созданное через npm create vite (или, упаси господь, npx create react app) после сборки представляет собой набор статики - index.html, пачка стилей, пачка жаба-скрипта. Это значит, что пользователь, запросивший ресурс , получает пустой html файл, после загрузки которого начинается генерация видимого контента. К сожалению, поисковые роботы ленивы и не очень хотят ждать, пока весь исполняемый код будет выполнен и DOM пополнится новыми узлами, поэтому максимум, что мы можем ему предложить - пара статичных мета-тегов внутри head. Это значит, что в поисковой выдаче мы можем оказаться только в виде 1 единственной ссылки (ведь внутренний роутинг так же будет проигнорирован). Такой подход называется CSR - Client Side Rendering
Чтобы сделать возможным индексацию нашего сайта, нам требуется, чтобы весь html генерировался на стороне сервера, и только потом отправлялся поисковому роботу, со всеми мета-тегами, заголовками, артиклями, ссылками и прочими прелестями веб-разработки. Это называется SSR - Server Side Rendering. Чтобы заставить React делать SSR, требуется дополнительный фреймворк.
Именно это я выяснил, когда захотел поглубже узнать про seo. Тогда было принято решение в срочном порядке внедрять какой-либо SSR-фреймворк, пока разработка не зашла слишком далеко. Из подобных я знал только Next.js, отзывы о котором в телеграмм-сообществе реакта мне были не по душе. Мне не очень хотелось сильно перелопачивать на половину готовый продукт, переходить на другой СТМ и так далее, поэтому я пошел искать. На проекте использовался Effector, и именно у этого сообщества я решил спросить о подобном решении. И мне посоветовали Vike.
Что за зверь?
Vike - до одури гибкий фреймворк на базе vite. Он позволяет делать SSR, CSR, SSG, а так же гибридные и изоморфные (при первом запросе страницы содержимое генерируется на стороне сервера, а при дальнейшем роутинге все происходит динамически на клиенте) веб-приложения.
В чем гибкость?
Универсальность - с его помощью можно делать SSR, CSR, SSG, гибридные и изоморфные приложения
Свобода выбора - он предлагает заготовленный для каждого ui-фреймворка (react, vue, angular, тп) свой адаптер, но не запрещает написать его самостоятельно.
Мощный роутинг - позволяет настроить роутинг на базе архитектуры папок, или же написать более сложную логику роутинга для каждой страницы отдельно
Четкое разделение ответственности - защита от несанкционированного доступа, выборка данных, настройка содержимого хеда, тайтла, дескрипшна, верстка лайаута, эффекты начала и конца рендеринга и прочее - каждый этап рендеринга регулируется индивидуально
Совместимость с СТМ - при изоморфном подходе не требуется вообще никаких манипуляций для корректной работы СТМ; Для строго SSR-подхода существуют адаптеры.
Перспективы - vike активно развивается, появляются новые фичи и приколюхи, а по интересующим вопросам и предложениям можно пообщаться в дискорде с его мейнтейнерами
А минусы будут?
0 статей на тему на русском языке. Причина, по которой существует эта статья
Трудности с обратной совместимостью. Для новых пользователей будет не заметно, но свою боль я описал в пункте с лайаутами
Немного не очевидна настройка с нуля, документация не дает исчерпывающих ответов на возникающие вопросы, приходится конкретно покумекать. Темплейты же наполнены излишним мусором, перед разработкой приходится провести чистку.
Создание проекта
В документации рекомендуется создавать проект с помощью Bati - инструмент для настройки шаблона vike.
Весьма удобно - можно не утруждаться самостоятельным протягиванием базовых зависимостей. Я на проекте использовал связку React + Tailwind + Express, используем же ее и здесь!
Структура проекта не сильно отличается от обычного vite шаблона, мы рассмотрим главное:
express-entry - входная точка в приложение, обычный node сервер. По моему опыту, если мы делаем чисто фронтенд, то этот файл меняется не часто
vike-handler - входная точка в процесс рендеринга. В старых шаблонах этот код не выносился из express-entry
Папка pages - в корне папки содержатся общие для всех страниц настройки, а так же сами страницы.
Путь к странице по умолчанию строится аналогично пути к файлу, т.е. странице http://example.com/products/edit будет соответствовать путь pages/products/edit.
Исключения составляет папка index - для нее url к странице будет http://example.com, а так же папка _error - в ней хранится страница ошибок 404 и 500, которая не имеет своего url-а.
В папке index находится файл +Page.ts, в котором и описана главная страница.
Здесь никакой логики сверх стандартной реактовской не используется, поэтому пойдем дальше.
Рассмотрим страницу todo. В папке по мимо +Page.ts содержится так же +data.ts и +config.ts.
В +config находятся настройки, аналогичные корневой +config (vike в принципе позволяет переопределять все +хуки на более глубокой вложенности). В данном случае здесь содержится флаг prerender=false, что, вообще-то говоря не имеет смысла, т.к. это настройка по умолчанию. Гораздо интереснее будет рассмотреть файл +data
Хук, предназначенный для запроса данных. Здесь мы можем делать fetch-и и axios.get-ы сколько душе угодно. Главное помнить, что при использовании ssr запрос происходит не на клиенте, а на сервере, а значит document и window будут не доступны.
После получения данных в +data.ts мы можем обратиться к этим данным на нашей странице с помощью хука useData()
Стоит упомянуть параметр pageContext. Начало он берет из pageContextInit файла vike-handler. Туда мы можем заранее передать все нужные нам параметры, в том числе и результаты запросов к апи (но так делать лучше не стоит). Далее этот контекст передается во все требуемые узлы в плоть до самих реакт-компонентов. На самом деле содержимое data является частью pageContext, но имеет более удобный способ доступа. Чаще всего pageContext нам интересен из-за содержимых в нем urlPathName, urlParsed.search (доступ к query-параметрам), is404 и еще некоторых полей. Так же стоит отметить, что pageContext отличается на стороне сервера и на стороне клиента - часть информации во избежание утечек на клиент не передается. Однако, это можно регулировать с помощью настройки в +config - passToClient:
В данном примере мы говорим vike о том, что хотим иметь доступ к полям user и is404 на стороне клиента.
Кстати, параметр user не является стандартным для vike, в примере он является пользовательским полем. Чтобы определить свое свойство внутри pageContext, нужно передать его в pageContextInit, а так же определить тип передаваемого поля в vike-pageContext.d.ts
Теперь мы можем получить доступ к полю user в наших компонентах следующим образом:const {user} = usePageContext()
Продолжим рассматривать файловую систему. Обратим внимание на папку star-wars. В ней находятся две подпапки - index и @id. Первая представляет собой страницу с url-ом http://example/star-wars, а вот вторая - это страница с параметром id. Это значит, что этой странице соответствуют любые пути вида http://example/star-wars/@id , где @id - любая подстрока. Подобные конструкции c path-параметрами нужны, например, для индивидуальных страниц постов блога. Доступ к @id в коде мы можем получить через pageContext.routeParams - объект, содержащий в себе все path-параметры. Их может быть много, например http://example/star-wars/@id/@variant/@anyParam содержит сразу 3 параметра.
Так же стоит упомянуть о "глобусах" - именно так их называет яндекс-переводчик на странице с документацией. Мы можем задать путь вида http://example/star-wars/* - который будет означать любой url, начинающийся с http://example/star-wars/. Доступ ко всему содержимому после мы можем получить через pageContext.routeParams['*']. Однако, ситуации, где нужны глобусы случаются очень редко.
Может возникнуть вопрос — а как описать файловую структуру так, чтобы добавить глобус? Ответ — никак, ведь просто звездочка не может быть названием папки. Зато, мы плавно подошли к возможностям, не фигурирующим в разбираемом шаблоне — хук +route
+route хук нужен для случаев, когда мы хотим задать сложный путь к странице, не прибегая к манипуляциям с файловой структурой, или же в построении пути существует нестандартная логика. +route бывает двух видов:
Обычная строка, например
export default 'blog/posts/@id/@variant/*'
Функция-квази-предикат, который возвращает либо false, либо объект routeParams. Функция роута должна быть простой, т.к. она выполняется каждый раз, когда происходит роутинг. Перед переходом между страницами vike собирает все существующие пути, а так же выполняет все роут-функции в попытках выявить соответствие указанному url-у. Если в процессе выполнения функции произошел return false, vike считает, что url не соответствует данному роуту.
Так же для ограничений роутинга существует хук +guard, смысл которого прост - не допустить не санкционированный доступ к странице.
Я, как правило, выполняю здесь только проверку юзера, и, хотя данный хук не запрещает использовать запросы к апи, если они необходимы, как правило, лучше их делать в +data, чтобы была возможность в будущем получить нужную информацию через useData.
Более того, всю логику данного хука можно перенести внутрь +data, но для лучшего понимания кода стоит по возможности совершать проверки именно здесь.
Можно обратить внимание на конструкцию throw render(). Данный подход позволяет во время процедуры рендеринга (только на сервере) заменить рисуемую страницу на другую без замены url-а. Это хорошо подходит для страниц ошибок 404 и 500. Так же вместо номера статуса в качестве аргумента можно использовать строку пути к другой странице. Я таким образом иногда отрисовываю страницы, изображающие отсутствие контента в силу некоторых причин (у юзера нет доступа к странице, список постов юзера пуст, потому что юзер еще не создавал постов, т.п.)
Есть другой вариант перехода между страницами на этапе рендеринга - throw redirect() - делает в принципе то же самое, но меняет url на указанный в аргументе. Подходит для случаев, когда в процессе рендеринга нам нужно, например, отправить пользователя на страницу авторизации.
Последний способ программно перевести юзера на другую страницу - navigate() - но нужна эта функция только на клиенте, и делает она простую вещь - редиректит пользователя на указанный url.
Стоит рассмотреть хуки +Head, +title и +description.
В файле +Head должен находиться компонент, описывающий содержимое тега <head>. При необходимости, мы можем переопределять данный компонент на более вложенных уровнях.
+title и +description создают соответствующие теги внутри <head> с описанным в них содержимым. В качестве таковых может выступать как строка, так и функция от pageContext, возвращающая строку. Можно сгенерировать title и description на базе данных, полученных в +data, например, для страницы с карточками товаров по конкретной категории.
Теперь мы подходим к самому интересному - +Layout. Из названия становится понятно, что это что-то вроде родительского компонента, hoc-а, применяемого сразу к нескольким страницам. В разрабатываемом мною продукте здесь описаны header, footer страницы, а так же некоторые дополнительные оболочки. На мой взгляд +Layout переживает не лучшие времена. Дело в том, что еще в версии 0.4.171 лайауты можно было переопределять на более низких уровнях вложенности. Это позволяло задать 1 глобальный лайаут, но по необходимости удалить его на других страницах. В более поздних версиях лайауты сделали наследуемыми, убрав полностью возможность переопределить вышестоящий и убив обратную совместимость. Надеюсь, мои комментарии в соответствующем ишью смогут привести к результату)
Итого
Выше я постарался раскрыть основные положения vike, которых достаточно для 90% всех задач. Более подробно о фреймворке вы можете почитать у них в документации. Пишите свои вопросы, я обязательно отвечу! Если наберется достаточно вопросов, я выпущу статью, где постараюсь подробно раскрыть интересующие вас аспекты:)
Читайте также моего коллегу:
Сага о внедрении DDD на Fastify в двух частях
Паттерн «Интерпретатор»: что такое и как использовать
Комментарии (19)
JerryI
06.10.2024 13:47+2Кто ж код скриншотами вставляет
isumix
06.10.2024 13:47Кстати на хабре до сих пор не поддерживается корректная подсветка JSX, да и цветовая схема не очень. Так что скриншоты лучше, либо код без подсветки, к сожалению. Хотя казалось бы, делов-то, если конечно они не свой велосипед пилят))
JerryI
06.10.2024 13:47Можно подобрать ближайшую. Там много каких языков нет. Когда скрин:
Выбивается из общего контента
Часто бывает мыльницы
Размеры прыгают, масштаб разный
Под светлую/темную тему не адаптируются
Нельзя проскроллить и скопировать (это боль)
isumix
06.10.2024 13:47SEO прекрасно подтягивается для SPA в 2024. Тут можно поспорить конечно, но ради нескольких процентов улучшения SEO, сильно усложнять приложение внедрением SSR, ну такое себе...)
bighorik Автор
06.10.2024 13:47+1Есть статьи про это? Когда я занимался вопросом, столкнулся с двумя вещами - во-первых, все статьи, посвященные тому что SPA может в SEO говорят о том что гугл очень пытается научиться, но чтобы прям заменить SSR - нет. Во-вторых, в реакт-сообществе весьма категорично ответили, что если нужно SEO - то SSR без вариантов, тем более что речи о индексации SPA идут еще со времен беззаботного резвления динозавров по полям, но пока-что от SSR не отказались.
Так же SPA проигрывает SSR-у, например, в сообщениях в телеграмме, когда ты вставляешь ссылку, а к ней подтягивается картинка, заголовок и еже с ним.isumix
06.10.2024 13:47Я тут логически рассуждаю, что можно на сервере нагенерить, тоже самое можно и на клиенте скриптом, расставив все хэдеры, пути, пропсы... Теоритически не должно быть никаких фундаментальных факторов мешающих это заиндексировать. Ну кроме огромных зависимостей и серверного времени кравлера. В общем самому интересно стало: https://www.reddit.com/r/javascript/comments/1fxigm3/askjs_are_spacsr_apps_less_optimal_for_seo_than/
isumix
06.10.2024 13:47Еще зависит от того, какое приложение мы делаем. Например для какого-нибудь лэндинга, на который пользователю пофиг, он не будет ждать долго и уйдет через 3 секунды, то нужен SSR и не нужен динамизм с Реактом. А вот для приложения, которое пользователь загуглил, то он и подождать сможет. А если ему еще в это время красивую заглушку показать, пререндеренную, см. про лэндинг выше, то будет вообще счастье.
Нет, скрипач не нужен...)
CapToYou
06.10.2024 13:47+2Google без особых проблем индексирует SPA:
Как Google обрабатывает JavaScript в процессе индексации веб-страниц
В целом из статьи не понятно почему мне стоит использовать SSR-фреймворк, да еще и малоизвестный Vike вместо Next.js.
strokoff
06.10.2024 13:47Вы бы сами прочитали статью и комментарии к ней. Вам бы и понятнее стало зачем SSR использовать, а то кажется что вы загуглили аргумент, но не стали читать его содержимое ограничившись заголовком
McHack
06.10.2024 13:47Затем, что NextJS частично проприетарный, а ещё очень громоздкий, не гибкий и слабоконфигурируемый
Статья слабо акцентирует внимание на эти аспекты, а зря. Если вам нужно сделать небольшое приложение, то тянуть слона в виде NextJS смысла на самом деле мало. У госов из-за проприетарщины может не пройти контроль и пиши пропало.
Плюс Next заточен под деплой на свой же Vercel (можно конечно куда угодно деплоить, но проще и быстрее на Vercel). Приложение на Vike можно задеплоить как и куда хочешь, предварительно построив архитектуру и подобрав инструментарий какой вам угодно.
Единственный плюс и одновременный минус Next. Это то, что все идёт в коробке и есть четко прописанные инструкции, что как и куда
Ambi2Rush
06.10.2024 13:47Я конечно не тестил, но судя по документации, Яндекс имеет весь функционал для индексирования SPA.
https://yandex.ru/support/webmaster/yandex-indexing/rendering.html
https://yandex.ru/support/webmaster/robot-workings/ajax-indexing.htmlstrokoff
06.10.2024 13:47+1Давно избитая тема. Индексация !== Высокие позиции в выдаче. Попробуйте найти хотябы один сайт SPA который по хотябы одному средне конкурентному/средне частотному запросу коммерческому который будет выше чем SSR сайт. Без разницы гугл или Яндекс. Не зря же вкладываются в разработку SSR в современном фронтенде. Все эти мифы про спа и сео ещё с 2015года живут
polRk
Зачем тут ненужный и бесполезный express? Чем ваше решение лучше аналогов?
bighorik Автор
В данном случае express используется просто как самый дефолтный вариант. Подойдет что угодно, что может ответить на get запрос :)
Отвечая на второй ваш вопрос, если сравнивать с Next-ом - под капотом Vite, а не webpack, работает с чем угодно, а не только с реактом, в разы гибче в плане политики рендеринга и т.п.
strokoff
В смысле с чем угодно? Vue имеет свой nuxt или svelte имеет свой ssr из коробки, собственно как и ангуляр. Так зачем и с чем в итоге кроме реакт имеет смысл использовать эту поделку? Что значит в разы гибче? Приведите сравнение с vue, что в нем будет в разы хуже, чем в vike? И ещё вопрос, вы когда начинаете проект в ентерпрайзе на фреймворке который даже до версии 1 не дорос, в целом думаете о будущем команды? О найме новичков на этот проект? Или это просто баловство которое можно себе позволить?