Share This
Связаться со мной
Крути в низ
Categories
//Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

server side rendering ssr kak dobavit v vue 3 vite prilozhenie 864245c - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Технический директор компании vverh.digital. JavaScript программист, любитель Kotlin и Swift. При разработке на реактивных фреймворках многие забывают о том, что итоговое приложение – это что-то ближе к SPA, а не классический сайт как «на WordPress». И когда дело доходит до SEO-продвижения, многие хватаются за голову, потому что поисковые системы плохо работают с такими ресурсами. Поэтому давайте сегодня познакомимся с технологией SSR, которая решит данную проблему.

server side rendering ssr kak dobavit v vue 3 vite prilozhenie 9720992 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Что такое SSR

SSR – (с англ. Server Side Rendering) технология, позволяющая выполнять на сервере JavaScript код для достижения каких-либо целей.

Зачем нужен SSR и что такое SEO

SSR в первую очередь необходим для продвижения сайта в интернете. Есть такое направление в маркетинге как SEO. И чаще всего, SSR необходим именно для этого.

SEO – (с англ. Search Engine Optimization) это оптимизация сайта под нужды поисковой системы. Само по себе, SEO продвижение – это целое самостоятельное направление в маркетинге с большой концентрацией капитала бизнеса, поэтому для многих это очень важная тема. Особенно если бизнес генерирует деньги в интернете.

Видите ли, когда поисковый робот делает запрос к сайту, сделанному на реактивных фреймворках по типу: Vue, React, Angular, то он видит примерно это:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie 789642c - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Никакого контента, только полупустой HTML. Хотя, если зайти на сайт с точки зрения обычного человека, мы увидим много текста и картинки.

А вот та же самая страница, но с уже включенным SSR:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie ebc1384 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Как видите, тут контент есть. Что вообще происходит?

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

Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека фронтендера» Интересно, перейти к каналу

Автор не прав, поисковые системы индексируют JavaScript сайты…

Раздел для тех, кто где-то видел или читал какие-то новости на этому тему. Да, вы в целом правы. Но есть огромное но

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

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

А теперь представьте классический сайт на PHP, C#, Python. Сделал запрос – получил контент. Все.

Как рендерить JavaScript на сервере

С помощью Node.js. Не любите Node.js? Извините, других способов у нас для вас нет.

Хотя внутри Node.js за исполнение JavaScript отвечает движок V8, можете его скачать с GitHub и засунуть в свой проект. Только учтите: V8 написан на С++. Как вы свяжите между собой кучу инструментов, мы представляем лишь примерно, но точно можем сказать что вам будет очень «весело».

Технически, возможно добавить SSR и в Laravel + Vue проект (помним, Laravel это PHP), но это будет выглядеть как-то так. Сомнительный монолит получится. Да и вам все равно потребуется Node.js, как ни крути. Так что, будем работать с Node.js.

Добавляем SSR во Vue приложение

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

Создаем Vue приложение

Примечание Автор использует Node.js 16.15.0.

Инициализируем Vue приложение с помощью команды:

         npm init vue@latest     

Далее нам зададут некоторые вопросы, отвечаем на них:

  1. Project name. Пишите любое.
  2. Add TypeScript? Нет.
  3. Add JSX Support? Нет.
  4. Add Vue Router for Single Page Application development? Обязательно да.
  5. Add Pinia for state management? Нет, если надо, позже сами добавите.
  6. Add Vitest for Unit Testing? Нет.
  7. Add an End-to-End Testing Solution? Нет.
  8. Add ESLint for code quality? Как хотите, автор использует всегда.
  9. Add Prettier for code formatting? Как хотите.

Теперь переходим в папку с проектом, устанавливаем пакеты и запускаем приложение в режиме разработки (команды вводите по порядку):

         cd “ваше название” npm install npm run dev     

У нас с вами появился такой проект, который нужен:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie aef4cdb - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Мы имеем App.vue как шаблон и несколько страниц добавленных через router/index.js: HomeView.vue и AboutView.vue.

server side rendering ssr kak dobavit v vue 3 vite prilozhenie 8bda000 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Если мы сейчас нажмем в браузере «Посмотреть код страницы», то не увидим никакого текста в нашем базовом приложении:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie a0b0b27 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Хотя в компонентах текст есть:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie d323065 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Компонент TheWelcome.vue, он вызывается внутри HomeView.vue.

Создаем сервер для рендеринга JavaScript

Для начала идем в package.json и добавляем туда строчку:

         "type": "module",     

Должно получится как-то так:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie 9f33b71 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Это для того чтобы в Node.js файлах использовать конструкцию import.

Теперь надо создать сервер. Пусть будет Express:

         npm install express     

В папке src создайте файл server.js со следующим содержимым:

server.js

         // Node.js utility import path from 'path' import fs from 'fs' import { fileURLToPath } from 'url'  // Vite import { createServer } from 'vite'  // Express import express from 'express'  // Helpers const __dirname = path.dirname(fileURLToPath(import.meta.url)) const resolve = (p) => path.resolve(__dirname, p)  const getIndexHTML = async () => {   const indexHTML = resolve('../index.html')   const html = await fs.promises.readFile(indexHTML, 'utf-8')   return html }  async function start() {   const manifest = null   const ssrServer = resolve('./main-server.js')    const app = express()   const router = express.Router()    const vite = await createServer({     server: { middlewareMode: true },     appType: 'custom'   })    app.use(vite.middlewares)    // Ловим все запросы, а вообще можно продублировать тут   // логику из src/router.js   router.get('/*', async (req, res, next) => {     try {       const url = req.url       let template = await getIndexHTML()       template = await vite.transformIndexHtml(url, template)        let render = (await vite.ssrLoadModule(ssrServer)).render              const [appHtml, preloadLinks] = await render(url, manifest)       const html = template         .replace(`<!--preload-links-->`, preloadLinks)         .replace('<!--app-html-->', appHtml)        res.status(200).set({ 'Content-Type': 'text/html' }).end(html)     } catch (e) {       vite.ssrFixStacktrace(e)       next(e)     }   })    // Routes   app.use('/', router)    app.listen(3000, () => {     console.log('Сервер запущен')   }) }  start()     

Давайте обсудим, что же здесь написано. Это очень важно.

На 22 строке функция start() запускает Express-сервер, предварительно запуская внутри себя Vite-сервер на 29 строке. Сам Vite-сервер – это некое дополнительное приложение, которое умеет компилировать Vue-файлы.

По идее, если через Node.js вызвать файл:

         node index.js     

в котором будем import файла с расширением .vue, то произойдет ошибка, так как нам нужно заранее предсобрать наше приложение особым способом через Vite (что мы и делаем).

Вообще, вся магия происходит с 40 по 51 строчку. В первую очередь, с помощью функции getIndexHTML(), которую мы чуть выше реализовали. Мы берем наш index.html из корня проекта, для того чтобы через регулярные выражения в нужное место установить отрендеренный контент. Да, нам нужно немного модернизировать index.html. Для этого вставьте под тег title конструкцию:

         <!--preload-links-->     

И между <div id="app"></div> конструкцию:

         <!--app-html-->     

Должно выйти так:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie 253ce6b - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

index.html

В preload-links полетят стили и еще всякие полезные ссылки, собираемые Vite. А в app-html, собранное с помощью SSR, – приложение.

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

За сам рендер JavaScript отвечает функция vite.ssrLoadModule(). В нее мы передаем путь до нашей специальной версии приложения – entry point для SSR. Да, мы сейчас говорим про файл main-server.js, которого у вас еще нету.

В папке src создайте еще один файл main-server.js с таким содержимым:

 main-server.js

         // Node.js import { basename } from 'node:path'  // Vue SSR import { createSSRApp } from 'vue' import { renderToString } from 'vue/server-renderer'  // App import App from './App.vue' import router from './router/index.js'  export async function render(url, manifest = null) {   const app = createSSRApp(App)   app.use(router)    await router.push(url)   await router.isReady()    // ctx - context. Плагин @vitejs/plugin-vue   // https://vitejs.dev/guide/ssr.html#generating-preload-directives   const ctx = {     modules: []   }   const html = await renderToString(app)   let preloadLinks = ''   if (manifest) {     renderPreloadLinks(ctx.modules, manifest)   }    return [html, preloadLinks] }  function renderPreloadLinks(modules, manifest) {   let links = ''   const seen = new Set()   modules.forEach((id) => {     const files = manifest[id]     if (files) {       files.forEach((file) => {         if (!seen.has(file)) {           seen.add(file)           const filename = basename(file)           if (manifest[filename]) {             for (const depFile of manifest[filename]) {               links += renderPreloadLink(depFile)               seen.add(depFile)             }           }           links += renderPreloadLink(file)         }       })     }   })   return links }  function renderPreloadLink(file) {   if (file.endsWith('.js')) {     return `<link rel="modulepreload" crossorigin href="${file}">`   } else if (file.endsWith('.css')) {     return `<link rel="stylesheet" href="${file}">`   } else if (file.endsWith('.woff')) {     return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`   } else if (file.endsWith('.woff2')) {     return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`   } else if (file.endsWith('.gif')) {     return ` <link rel="preload" href="${file}" as="image" type="image/gif">`   } else if (file.endsWith('.jpg') || file.endsWith('.jpeg')) {     return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`   } else if (file.endsWith('.png')) {     return ` <link rel="preload" href="${file}" as="image" type="image/png">`   } else {     return ''   } }     

По документации Vite, функция vite.ssrLoadModule() возвращает другие экспортируемые функции из передаваемого файла. Поэтому внутри main-server.js мы объявляем функцию render() и в ней напишем классический SSR сервер из документации Vue.js.

Сама функция render() будет вызываться из файла server.js.

Внутри main-server.js у многих могут вызывать вопросы две функции: renderPreloadLinks() и renderPreloadLink(). Хотя они и выглядят страшно, но на самом деле выполняют простую роль: они помогают нам и подготавливают ссылки на .css файлы. Все ссылки на чанки стилей будут находиться в манифесте. Мы его просто тут читаем. Понимаем, вопросов много, но пока у нас нет манифеста, мы его сделаем чуть позже, и все станет сразу в разы понятней.

К сожалению, это еще не все (хотя уже финишная прямая). Даже если мы сейчас попытаемся запустить сервер, то ничего хорошего не произойдет. Нам надо еще перенастроить наш router/index.js. Для этого откройте этот файл.

Смотрите на 5 строку, раздел history. Тут используется функция createWebHistory(). Под капотом у этой функции есть использование глобальных переменных document и window. Только вот беда: когда мы будем собирать наше приложение через SSR-мод с помощью Node.js, мы не сможем обратиться к этим переменным. Просто потому, что в Node.js нет их. Вместо window в Node.js есть global и process, но там совсем другое содержимое. А document вообще является DOM API, которого тем более там нет… это же не браузер.

Поэтому мы должны поменять createWebHistory() на сreateMemoryHistory(), но только для SSR, дабы в обычном режиме приложение не сломалось. Поэтому модернизируйте файл router/index.js таким способом:

router/index.js

         import { createRouter, createWebHistory, createMemoryHistory } from 'vue-router' import HomeView from '../views/HomeView.vue'  const baseUrl = import.meta.env.BASE_URL const history = import.meta.env.SSR ? createMemoryHistory(baseUrl) : createWebHistory(baseUrl)  const router = createRouter({   history,   routes: [     {       path: '/',       name: 'home',       component: HomeView     },     {       path: '/about',       name: 'about',       component: () => import('../views/AboutView.vue')     }   ] })  export default router     

Теперь можете запустить наше творение командой:

         node ./src/server.js     

Во-первых, сервер запустился под адресом localhost:3000, и если вы перейдете на него и откроете исходный код, то увидите результат своего труда:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie 91d9534 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

Можете проскролить вправо и там будет еще контент из компонентов

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

Во-вторых, если нажимать F5, то как-то некрасиво встают стили. Мы это исправим за счет манифеста. В режиме разработки мы поработаем и так, а для production сделаем все чуть красивее.

В-третьих, если вы меняете файлы, Vite подхватывает изменения и делает Hot Reload. Ну, кроме файла server.js… тут, если хотите, то же самое – надо поставить nodemon и запускать server.js уже через него. Как-то так:

         npm install -g nodemon nodemon ./src/server.js     

P.S: Это по желанию.

Финал: сборка для production

Замените содержимое server.js на новое:

server.js

         // Node.js utility import path from 'path' import fs from 'fs' import { fileURLToPath } from 'url'  // Vite import { createServer } from 'vite'  // Express import express from 'express'  // eslint-disable-next-line no-undef const isProd = process.env.NODE_ENV === 'production'  // Helpers const __dirname = path.dirname(fileURLToPath(import.meta.url)) const resolve = (p) => path.resolve(__dirname, p)  const getIndexHTML = async () => {   const indexHTML = isProd     ? resolve('../dist/client/index.html')     : resolve('../index.html')   const html = await fs.promises.readFile(indexHTML, 'utf-8')   return html }  async function start() {   const manifest = isProd      ? JSON.parse(fs.readFileSync(resolve('../dist/client/ssr-manifest.json'), 'utf-8'))     : null    const ssrServer = isProd     ? resolve('../dist/server/main-server.js')     : resolve('./main-server.js')    const app = express()   const router = express.Router()    const vite = await createServer({     // eslint-disable-next-line no-undef     root: process.cwd(),     server: { middlewareMode: true },     appType: 'custom'   })    if (isProd) {     app.use(express.static('dist/client', { index: false }))   }    app.use(vite.middlewares)    // Ловим все запросы, а вообще можно продублировать тут   // логику из src/router.js   router.get('/*', async (req, res, next) => {     try {       const url = req.url       let template = await getIndexHTML()       template = await vite.transformIndexHtml(url, template)        let render = (await vite.ssrLoadModule(ssrServer)).render              const [appHtml, preloadLinks] = await render(url, manifest)       const html = template         .replace(`<!--preload-links-->`, preloadLinks)         .replace('<!--app-html-->', appHtml)        res.status(200).set({ 'Content-Type': 'text/html' }).end(html)     } catch (e) {       vite.ssrFixStacktrace(e)       next(e)     }   })    // Routes   app.use('/', router)    app.listen(3000, () => {     console.log('Сервер запущен')   }) }  start()     

Тут не так много правок, как может показаться. В самом вверху мы добавили переменную isProd.

С помощью этой переменной мы будем понимать в каком режиме мы сейчас функционируем. По-хорошему, для production нужно заранее собрать наше приложение через Vite. После сборки оно помещается в папку dist и мы будем просто тянуть файлы оттуда. Посмотрите на строки 20, 28 и 32. Тут как раз у нас еще и манифест появился.

Теперь давайте соберем наше приложение в боевом режиме. Давайте внесем корректировки в package.json:

Добавить в  package.json

         "dev": "node ./src/server.js", "serve": "NODE_ENV=production node ./src/server.js", "build": "npm run build:client && npm run build:server", "build:client": "vite build --ssrManifest --outDir dist/client", "build:server": "vite build --ssr src/main-server.js --outDir dist/server",      

Должно получится так:

server side rendering ssr kak dobavit v vue 3 vite prilozhenie f1636d9 - Server Side Rendering (SSR): как добавить в Vue 3 + Vite приложение

package.json

Теперь вместо:

         node ./src/server.js     

Можно использовать:

         npm run dev     

А для production есть команда:

         npm run serve     

Но только перед тем как ее запустить, выполните команду:

         npm run build     

Ибо без сборки нечего «обслуживать».

В целом на этом все, можем поздравить вас с реализацией своего SSR без всяких фреймворков.

Бонус: альтернативные способы внедрения SSR

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

Например, в рамках Vue 3 существуют такие инструменты как Nuxt и Quasar. Данные инструменты позволяют не создавать всякие Express сервера, а просто работать с Vue, как привыкли. Минусы такого подхода лишь в том, что не вы сами настраиваете Express сервер, а разработчик фреймворка. Поэтому, вы, как программист, придерживаетесь чужой логики (но это не всегда плохо).

***

Итог

Надеемся, этот душный туториал не прошел зря, и вы научились магии SSR в JavaScript. Вот ссылка готового проекта на GitHub. Если будут вопросы, пишите в комментариях, автор постарается помочь.

Материалы по теме

  • ☕ 7 советов изучающему Vue.js новичку
  • ☕ 5 продвинутых советов по повышению производительности Vue
  • ⚔ Vue vs React vs Angular: какой фронтенд-фреймворк выбрать?
  • 🔩 Полный фуллстек: пишем сайт на Django, Vue и GraphQL

  • 1 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close