Skip to content

5. Приклад [nuxt-02]: серверні та клієнтські сторінки

У цьому проєкті ми показуємо:

  • що сторінка, побудована клієнтом, може візуально відрізнятися від тієї, що отримана від сервера. У результаті відбувається швидка зміна сторінки, помітна для користувача, що негативно впливає на ергономіку додатка. Тому такого варіанту слід уникати;
  • рішення, за якого клієнтська сторінка відтворює ту саму сторінку, що й та, яку надіслав сервер;

Проєкт [nuxt-02] спочатку отримано шляхом копіювання проєкту [nuxt-01].

Image

До проекту додано папку [store], а також дві нові сторінки. Ми повернемося до цього пізніше.

5.1. Сторінка [index]

5.1.1. Код сторінки

Код сторінки [index] виглядає наступним чином:


<!-- головна сторінка -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення-->
    <b-alert slot="right" show variant="warning"> Home - value= {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-undef */
/* eslint-disable no-console */
/* eslint-disable nuxt/no-env-in-hooks */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Home',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[home beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[home created]')
    // тільки сервер
    if (process.server) {
      this.value = 10
    }
    // клієнт і сервер
    console.log('value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[home beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[home mounted]')
  }
}
</script>

Коментарі

  • рядок 7: сторінка [index] відображатиме значення своєї властивості [value] (рядок 28);
  • рядки 36–45: тут слід пам’ятати, що функція [created] виконується як на стороні сервера, так і на стороні клієнта. У рядках 40–42 сервер змінить значення властивості [value] на 10. Клієнт, у свою чергу, не змінює це значення. Ми просто хочемо дізнатися, чи зберігається це значення на стороні клієнта. Ми побачимо, що ні;

5.1.2. Виконання

Ми змінюємо файл [/nuxt.config.js], щоб виконати проект [nuxt-02]:


...
  // каталог вихідного коду
  srcDir: 'nuxt-02',
  // маршрутизатор
  router: {
    // кореневий каталог URL додатка
    base: '/nuxt-02/'
  },
  // сервер
  server: {
    // порт служби, за замовчуванням 3000
    port: 81,
    // мережеві адреси, на яких відбувається прослуховування, за замовчуванням localhost: 127.0.0.1
    // 0.0.0.0 = усі мережеві адреси комп’ютера
    host: 'localhost'
  }
...

Виконуємо проект [1]:

Image

Після цього відображається сторінка [index] [2-3]. На ній протягом кількох секунд відображається значення [10], а потім — значення [0]. Що сталося?

Крок 1

Сервер запускається першим. Він виконує код сторінки [index]:


export default {
  name: 'Home',
  // використовувані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[home beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[home created]')
    // тільки сервер
    if (process.server) {
      this.value = 10
    }
    // клієнт і сервер
    console.log('value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[home beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[home mounted]')
  }
}
  • через рядок 23 властивість [value] у рядку 10 набуває значення 10;

Це можна перевірити, переглянувши вихідний код сторінки, отриманої браузером (опція [code source] у браузері):


<!doctype html>
<html data-n-head-ssr>
<head>
  <title>Introduction à [nuxt.js]</title>
  <meta data-n-head="ssr" charset="utf-8">
  <meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
  <meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
  <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
  <base href="/nuxt-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
  ....
</head>
<body>
  <div data-server-rendered="true" id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
              <h4>[nuxt-02] : page serveur, page client</h4>
            </div> <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link active nuxt-link-active">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page2" target="_self" class="nav-link">
                        Page 2
                      </a>
                    </li>
                  </ul>
                </div> <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-warning">
                    Home - value= 10
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>window.__NUXT__ = ....;</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • рядок 46: на отриманій сторінці [value] мав значення 10;

крок 2

Відомо, що після отримання сторінки скрипти в рядках 57–60 беруть контроль і змінюють поведінку отриманої сторінки, а також інформацію, що відображається, як у цьому випадку. Ці скрипти утворюють клієнт, який також виконує код сторінки [index] — той самий код, що й на сервері:


export default {
  name: 'Home',
  // використовувані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[home beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[home created]')
    // тільки сервер
    if (process.server) {
      this.value = 10
    }
    // клієнт і сервер
    console.log('value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[home beforeMount]')
  },
  mounted() {
    // лише клієнт
    console.log('[home mounted]')
  }
}
  • щоб зрозуміти, що відбувається, потрібно усвідомити, що клієнт [nuxt] не виконуватиме рядки 22–24 (process.server=false);
  • у класичному додатку [vue] властивість [value] у рядку 10 залишається рівною 0. Тому, коли клієнт переходить на отриману сторінку, відображуване значення стає [0];

Значення, згенероване сервером [nuxt] для властивості [value], виявилося марним.

5.2. Сторінка [page1]

5.2.1. Магазин [Vuex]

Ми додали папку [store] до проєкту [nuxt-02]:

Image

Наявність цього файлу призводить до того, що [nuxt] автоматично реалізує сховище [Vuex]. Цей магазин реалізується файлом [index.js]. Тут файл [index.js] має такий вигляд:


export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state, inc) {
    state.counter += inc
  }
}

[nuxt] реалізує магазин [Vuex] на основі вмісту [index.js]:

  • рядки 1–3: визначення стану [state] сховища. Цей стан повертається функцією. Тут стан має лише одну властивість — лічильник у рядку 2. Експортована функція повинна називатися [state];
  • рядки 5–9: можливі операції над станом магазину. Вони називаються [mutations]. Тут операція [increment] дозволяє збільшити значення властивості [counter] на величину [inc]. Експортований об’єкт повинен називатися [mutations];

[store] Vuex, реалізований [nuxt], доступний у різних місцях. У поданнях він доступний у властивості [this.$store].

5.2.2. Код сторінки

Як і сторінка [index], сторінка [page1] відображатиме значення лічильника Vuex:


<!-- сторінка 1 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення-->
    <b-alert slot="right" show variant="primary"> Page 1 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[home beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[home created]')
    // тільки сервер
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[home beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[home mounted]')
  }
}
</script>

Коментарі

  • рядки 38–40: сервер збільшить лічильник на 25;
  • рядок 42: і сервер, і клієнт виведуть значення лічильника;
  • рядок 7: значення лічильника відображається;

Читаючи цей код, слід розуміти дві речі:

  • код, що виконується, однаковий як для сервера, так і для клієнта;
  • але об’єкт [this] не є однаковим: на стороні сервера існує версія [this], а на стороні клієнта — інша, [client];

Ми намагаємося з’ясувати, чи [this.$store] на сервері є тим самим, що й [this.$store] на стороні клієнта. Оскільки сервер запускається першим (під час запуску програми), це зводиться до такого питання: чи передається клієнту [store], ініціалізований сервером?

5.2.3. Виконання

Запускаємо проект [nuxt-02] і вручну вводимо [localhost:81/nuxt-02/page1], щоб звернутися до сервера. Як і під час запуску сторінки [index]:

  • сервер виконує сторінку [page1.vue];
  • надсилає згенеровану сторінку до браузера. Вона відображається;
  • вбудовані в надіслану сторінку клієнтські скрипти беруть управління на себе та знову виконують сторінку [page1.vue];
  • відображена сторінка при цьому змінюється;

Кінцевий результат такий:

Image

Цього разу відображене значення дійсно відповідає тому, що було встановлено сервером, і візуально не помітно, що сторінка «підскакує» через зміну клієнтом значення, яке відображає сервер. Що ж сталося цього разу?

Сервер виконав наступну сторінку [page1]:


...

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // використовувані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page1 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[page1 created]')
    // тільки сервер
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page1 mounted]')
  }
}
</script>
  • рядки 30–32 були виконані без помилок. Це означає, що й на стороні сервера [this.$store] також вказує на змінну [Vuex]. Рядок 31 змінив значення лічильника цієї змінної на 25;
  • після чого сторінка була надіслана клієнту;

Якщо подивитися на сторінку, отриману клієнтом, можна побачити такі елементи:


<!doctype html>
<html data-n-head-ssr>
<head>
  <title>Introduction à [nuxt.js]</title>
  <meta data-n-head="ssr" charset="utf-8">
  <meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
  <meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
  <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
  <base href="/nuxt-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
  ...
</head>
<body>
  <div data-server-rendered="true" id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
              <h4>[nuxt-02] : page serveur, page client</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link active nuxt-link-active">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page2" target="_self" class="nav-link">
                        Page 2
                      </a>
                    </li>
                  </ul>
                </div> <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-primary">
                    Page 1 - value = 25
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
  window.__NUXT__ = (function (a, b, c) {
      return {
        layout: "default", data: [{}], error: null, state: { counter: 25 }, serverRendered: true,
        logs: [
          { date: new Date(1574085336802), args: ["[home beforeCreate]"], type: a, level: b, tag: c },
          { date: new Date(1574085336839), args: ["[home created]"], type: a, level: b, tag: c },
          { date: new Date(1574085336869), args: ["value=", "25"], type: a, level: b, tag: c }
        ]
      }
    }("log", 2, ""));</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • рядок 47: значення, надіслане сервером;
  • рядок 60: бачимо, що стан сховища [Vuex] було вбудовано у сторінку. Це дозволить клієнту, який запуститься після отримання сторінки, відтворити нове сховище [Vuex] із початковим значенням лічильника 25;

Після отримання та відображення сторінки, отриманої від сервера, клієнт бере на себе управління і, у свою чергу, виконує сторінку [page1]:


...

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page1',
  // використовувані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page1 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[page1 created]')
    // тільки сервер
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page1 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page1 mounted]')
  }
}
</script>
  • рядок 34: властивість [value] у рядку 18, у свою чергу, отримує значення лічильника 25;

Таким чином, магазин даних [nuxt] дозволяє серверу передавати інформацію клієнту під час початкового завантаження сторінки, коли її шукають на сервері. Нагадаємо, що після отримання цієї сторінки сервер більше не задіюється, і додаток працює як звичайний додаток [vue] у режимі SAP.

5.3. Сторінка [page2]

На сторінці [page2] ми демонструємо інший спосіб, за допомогою якого:

  • сервер включає обчислені дані на сторінку;
  • клієнт не змінював цю інформацію;

5.3.1. Код сторінки

Код сторінки [page2] змінюється наступним чином:


<!-- сторінка 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page2',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // хто виконує цей код?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // тільки для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом однієї секунди
        setTimeout(() => {
          // цей результат буде включено до властивостей [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page2 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[page2 created]')
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page2 mounted]')
  }
}
</script>
  • рядок 7: сторінка відображає значення властивості з іменем [value];
  • властивість [value] не існує як елемент об’єкта, що повертається функцією [data]. У даному випадку ця функція не існує. Властивість [value] створюється динамічно у рядку 36;
  • рядок 25: функція [asyncData] є функцією [nuxt]. Як випливає з назви, зазвичай це асинхронна функція. Її звичайне призначення — отримувати зовнішні дані. [nuxt] гарантує, що сторінка не буде надіслана до браузера клієнта, доки функція [asyncData] не поверне свої асинхронні дані;
  • функція [asyncData] отримує як параметр контекст [nuxt]. Цей об’єкт містить велику кількість інформації про додаток [nuxt]. Ми дізнаємося про це в наступних розділах;
  • рядок 31: реалізуємо функцію [asyncData] із використанням [Promise] (див. документ |Вступ до мови ECMASCRIPT 6 на прикладі|). Конструктор цього класу приймає як параметр асинхронну функцію, яка:
    • позначає успіх, повертаючи дані за допомогою функції [resolve]. Об’єкт, повернений цією функцією, автоматично включається до властивостей [data] сторінки;
    • повідомляє про невдачу, повертаючи помилку за допомогою функції [reject];
  • рядок 34: імітується асинхронна функція за допомогою функції [setTimeout]. Ця функція повертає об’єкт [{ value: 87 }] (рядок 36) через секунду (рядок 31) завдяки функції [resolve], яка повідомляє про успішне виконання функції [Promise]. Об’єкт, повернений асинхронною функцією, автоматично включається до властивостей [data] сторінки. І саме цю властивість відображає рядок 7;
  • рядок 27: ми побачимо, що функція [asyncData] виконується сервером, а не клієнтом;
  • рядок 29: ініціалізація властивості [value] здійснюється сервером;

Примітка: об’єкт [this] невідомий у функції [asyncData], оскільки об’єкт, що інкапсулює компонент [vue], ще не створено;

5.3.2. Виконання

Запускаємо проект [nuxt-02] і вручну вводимо [localhost:81/nuxt-02/page2], щоб сервер отримав запит. Як і під час запуску сторінки [index]:

  • сервер виконує сторінку [page2.vue];
  • надсилає згенеровану сторінку до браузера. Вона відображається;
  • вбудовані в надіслану сторінку клієнтські скрипти беруть на себе управління та знову виконують сторінку [page2.vue];
  • відображена сторінка при цьому змінюється;

Кінцевий результат такий:

Image

Цього разу відображене значення дійсно відповідає тому, що було встановлено сервером, і візуально не помітно, що сторінка «стрибає» через зміну клієнтом значення, яке відображає сервер. Що ж сталося цього разу?

Сервер виконав наступну сторінку [page2]:


<!-- сторінка 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page2',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // хто виконує цей код?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // лише для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом однієї секунди
        setTimeout(() => {
          // цей результат буде включено до властивостей [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page2 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[page2 created]')
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page2 mounted]')
  }
}
</script>

Саме рядок 36 визначив значення, яке відображається у рядку 7. Отже, саме це отримав браузер клієнта. Точніше кажучи, він отримує таку сторінку:


<!doctype html>
<html data-n-head-ssr>
<head>
  <title>Introduction à [nuxt.js]</title>
  <meta data-n-head="ssr" charset="utf-8">
  <meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
  <meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
  <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
  <base href="/nuxt-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
  ...
</head>
<body>
  <div data-server-rendered="true" id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
              <h4>[nuxt-02] : page serveur, page client</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page2" target="_self" class="nav-link active nuxt-link-active">
                        Page 2
                      </a>
                    </li>
                  </ul>
                </div>
                <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-secondary">
                    Page 2 - value = 87
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
  window.__NUXT__ = (function (a, b, c) {
      return {
        layout: "default", data: [{ value: 87 }], error: null, state: { counter: 0 }, serverRendered: true,
        logs: [
          { date: new Date(1574096608555), args: ["asyncData, client=", "false", "serveur=", "true"], type: a, level: b, tag: c },
          { date: new Date(1574096608575), args: ["[page2 beforeCreate]"], type: a, level: b, tag: c },
          { date: new Date(1574096608599), args: ["[page2 created]"], type: a, level: b, tag: c }
        ]
      }
    }("log", 2, ""));</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • рядок 48: бачимо, що значення на отриманій сторінці дорівнює 87;
  • рядок 61: у відповіді сервера бачимо два об’єкти: [data] та [state]:
    • [state] — це стан сховища [Vuex]. Воно було створено на основі вмісту папки [store] додатка [nuxt-02];
    • [data] містить властивості, створені сервером за допомогою функції [asyncData]. Тут ми бачимо властивість [value : 87], створену сервером. Скрипти клієнта інтегрують цю властивість у властивості сторінки [page2];

Повернемося до коду сторінки [page2]:


<!-- сторінка 2 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Page 2 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page2',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  asyncData(context) {
    // хто виконує цей код?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // лише для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом однієї секунди
        setTimeout(() => {
          // цей результат буде включено до властивостей [data]
          resolve({ value: 87 })
          // log
          console.log('asynData terminée')
        }, 1000)
      })
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page2 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    console.log('[page2 created]')
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page2 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page2 mounted]')
  }
}
</script>
  • у рядку 7 використовується властивість [value]. Однак на сторінці не визначено жодної властивості з іменем [value]. Проте клієнтські скрипти автоматично створили цю властивість завдяки об’єкту [data: [{ value: 87 }]], отриманому від сервера;

Крім того, журнали показують, що функція [asyncData] не була виконана клієнтом:

Image

Функція [asyncData] була виконана сервером [1], але не клієнтом [2]. Крім того, слід зазначити, що функції життєвого циклу не виконуються сервером до завершення функції [asyncData]. Щоб перевірити це, можна збільшити час очікування у функції [asyncData].

5.4. Сторінка [page3]

Ми додаємо до нашого додатка нову сторінку [page3]:

Image

5.4.1. Компонент [navigation]

Компонент [vavigation] змінено, щоб забезпечити перехід на нову сторінку:


<template>
  <!-- меню Bootstrap із трьома пунктами -->
  <b-nav vertical>
    <b-nav-item to="/" exact exact-active-class="active">
      Home
    </b-nav-item>
    <b-nav-item to="/page1" exact exact-active-class="active">
      Page 1
    </b-nav-item>
    <b-nav-item to="/page2" exact exact-active-class="active">
      Page 2
    </b-nav-item>
    <b-nav-item to="/page3" exact exact-active-class="active">
      Page 3
    </b-nav-item>
  </b-nav>
</template>

5.4.2. Код [page3]

Код сторінки [page3] такий:


<!-- сторінка 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page3',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // хто виконує цей код?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // лише для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом однієї секунди
        setTimeout(() => {
          // успіх
          resolve()
        }, 1000)
      }).then(() => {
        // змінюємо значення в змінній
        context.store.commit('increment', 28)
      })
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page3 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page3 mounted]')
  }
}
</script>
  • рядок 30: функція [fetch] працює аналогічно до функції [asyncData]:
    • вона виконується перед функціями життєвого циклу;
    • об’єкт [this] у цій функції не визначений;
    • її робота є асинхронною;
    • життєвий цикл не починається, доки асинхронна функція не поверне свій результат;
    • результат тут повертається методом [then] функції [Promise], рядок 43;
    • функція [fetch] отримує параметр [context]. Він представляє поточний контекст [nuxt];
  • рядок 30: серед своїх численних властивостей об’єкт [context] має властивість [store], яка представляє стор [Vuex] додатка;
  • рядок 41: штучно позначаємо успішне виконання [Promise] через секунду (див. документ |Вступ до мови ECMASCRIPT 6 на прикладі|);
  • рядок 45: після цього виконується метод [then]. У ньому збільшується лічильник [store];

5.4.3. Виконання

Виконуємо проект [nuxt-02] і вручну вводимо [localhost:81/nuxt-02/page3], щоб сервер отримав запит. Як і під час запуску сторінки [index]:

  • сервер виконує сторінку [page3.vue];
  • надсилає згенеровану сторінку до браузера. Вона відображається;
  • вбудовані в надіслану сторінку клієнтські скрипти беруть контроль і знову виконують сторінку [page3.vue];
  • відображена сторінка при цьому змінюється;

Кінцевий результат такий:

Image

Відображене значення дійсно відповідає тому, що було встановлено сервером, і візуально не помітно, що сторінка «підскакує» через зміну клієнтом значення, яке відображає сервер. Що ж сталося цього разу?

Сервер виконав наступну сторінку [page3]:


<!-- сторінка 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page3',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // хто виконує цей код?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // лише для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом однієї секунди
        setTimeout(() => {
          // успіх
          resolve()
        }, 1000)
      }).then(() => {
        // змінюємо значення в змінній
        context.store.commit('increment', 28)
        // журнал
        console.log('fetch commit terminé')
      })
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page3 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page3 mounted]')
  }
}
</script>
  • рядок 45: асинхронна функція [fetch] є першою з наведених вище функцій, що виконується. Вона отримує як параметр об’єкт із назвою [context], який є поточним контекстом [nuxt]. Серед численних властивостей цього об’єкта властивість [context.store] представляє магазин [Vuex];
  • рядок 45: у асинхронній функції [fetch] сервер встановлює лічильник сховища на 28;
  • рядок 56: під час виконання функції [created] [nuxt] гарантує, що асинхронна функція [fetch] завершила свою роботу;
  • рядок 58: значення лічильника жалюзі присвоюється властивості [value] з рядка 27;
  • рядок 7: відображення значення [value], тобто лічильника змінної;

Браузер клієнта отримує таку сторінку:


<!doctype html>
<html data-n-head-ssr>
<head>
  <title>Introduction à [nuxt.js]</title>
  <meta data-n-head="ssr" charset="utf-8">
  <meta data-n-head="ssr" name="viewport" content="width=device-width, initial-scale=1">
  <meta data-n-head="ssr" data-hid="description" name="description" content="ssr routing loading asyncdata middleware plugins store">
  <link data-n-head="ssr" rel="icon" type="image/x-icon" href="/favicon.ico">
  <base href="/nuxt-02/">
  <link rel="preload" href="/nuxt-02/_nuxt/runtime.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/commons.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/vendors.app.js" as="script">
  <link rel="preload" href="/nuxt-02/_nuxt/app.js" as="script">
  ...
</head>
<body>
  <div data-server-rendered="true" id="__nuxt">
    <div id="__layout">
      <div class="container">
        <div class="card">
          <div class="card-body">
            <div role="alert" aria-live="polite" aria-atomic="true" align="center" class="alert alert-success">
              <h4>[nuxt-02] : page serveur, page client</h4>
            </div>
            <div>
              <div class="row">
                <div class="col-2">
                  <ul class="nav flex-column">
                    <li class="nav-item">
                      <a href="/nuxt-02/" target="_self" class="nav-link">
                        Home
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page1" target="_self" class="nav-link">
                        Page 1
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page2" target="_self" class="nav-link">
                        Page 2
                      </a>
                    </li>
                    <li class="nav-item">
                      <a href="/nuxt-02/page3" target="_self" class="nav-link active nuxt-link-active">
                        Page 3
                      </a>
                    </li>
                  </ul>
                </div> <div class="col-10">
                  <div role="alert" aria-live="polite" aria-atomic="true" class="alert alert-secondary">
                    Page 3 - value = 28
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
  window.__NUXT__ = (function (a, b, c) {
      return {
        layout: "default", data: [{}], error: null, state: { counter: 28 }, serverRendered: true,
        logs: [
          { date: new Date(1574169916025), args: ["fetch, client=", "false", "serveur=", "true"], type: a, level: b, tag: c },
          { date: new Date(1574169917038), args: ["fetch commit terminé"], type: a, level: b, tag: c },
          { date: new Date(1574169917137), args: ["[page3 beforeCreate]"], type: a, level: b, tag: c },
          { date: new Date(1574169917167), args: ["[page3 created], value=", "28"], type: a, level: b, tag: c }
        ]
      }
    }("log", 2, ""));</script>
  <script src="/nuxt-02/_nuxt/runtime.js" defer></script>
  <script src="/nuxt-02/_nuxt/commons.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/vendors.app.js" defer></script>
  <script src="/nuxt-02/_nuxt/app.js" defer></script>
</body>
</html>
  • рядок 52: бачимо, що значення на отриманій сторінці дорівнює 28;
  • рядок 65: у відповіді сервера видно, що сервер надіслав клієнту стан [state] сховища [Vuex]. Завдяки цій інформації клієнтські скрипти зможуть відтворити сховище [Vuex];

Скрипти клієнта, у свою чергу, виконають код сторінки [page3]:


<!-- сторінка 3 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Page 3 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */
/* eslint-disable nuxt/no-timing-in-fetch-data */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page3',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  fetch(context) {
    // хто виконує цей код?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // лише для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом однієї секунди
        setTimeout(() => {
          // успіх
          resolve()
        }, 1000)
      }).then(() => {
        // змінюємо значення в змінній
        context.store.commit('increment', 28)
        // журнал
        console.log('fetch commit terminé')
      })
    }
  },
  // життєвий цикл
  beforeCreate() {
    // клієнт і сервер
    console.log('[page3 beforeCreate]')
  },
  created() {
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('[page3 created], value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page3 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page3 mounted]')
  }
}
</script>
  • рядок 58: функція [created], що виконується клієнтом, присвоює значення лічильника властивості [value] у рядку 27;
  • у рядку 7 відображається це значення. Оскільки воно збігається з тим, що надіслано сервером, сторінка не «підскакує» внаслідок зміни;

Крім того, журнали показують, що функція [fetch] не була виконана клієнтом:

Image

Функція [fetch] була виконана сервером [1], але не клієнтом [2]. Крім того, можна зазначити, що функції життєвого циклу не виконуються сервером до завершення функції [fetch] [3]. Щоб це перевірити, можна збільшити час очікування у функції [fetch].

На сторінках [page1] та [page3] було продемонстровано два методи використання сховища [Vuex] для передачі інформації від сервера до клієнта. Можна задатися питанням, чи є вони еквівалентними. Щоб це перевірити, ми створюємо сторінку [page4].

5.5. Сторінка [page4]

Ми додаємо нову сторінку [page4] до нашого додатка:

Image

5.5.1. Компонент [navigation]

Компонент [navigation] змінено, щоб забезпечити перехід на нову сторінку:


<template>
  <!-- меню Bootstrap з п’ятьма пунктами -->
  <b-nav vertical>
    <b-nav-item to="/" exact exact-active-class="active">
      Home
    </b-nav-item>
    <b-nav-item to="/page1" exact exact-active-class="active">
      Page 1
    </b-nav-item>
    <b-nav-item to="/page2" exact exact-active-class="active">
      Page 2
    </b-nav-item>
    <b-nav-item to="/page3" exact exact-active-class="active">
      Page 3
    </b-nav-item>
    <b-nav-item to="/page4" exact exact-active-class="active">
      Page 4
    </b-nav-item>
  </b-nav>
</template>

5.5.2. Код [page4]

Код сторінки [page4] такий:


<!-- сторінка 4 -->
<template>
  <Layout :left="true" :right="true">
    <!-- навігація -->
    <Navigation slot="left" />
    <!-- повідомлення -->
    <b-alert slot="right" show variant="secondary"> Page 4 - value = {{ value }} </b-alert>
  </Layout>
</template>

<script>
/* eslint-disable no-console */

import Navigation from '@/components/navigation'
import Layout from '@/components/layout'

export default {
  name: 'Page4',
  // використані компоненти
  components: {
    Layout,
    Navigation
  },
  data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  async beforeCreate() {
    // клієнт і сервер
    console.log('[page4 beforeCreate]')
    // лише для сервера
    if (process.server) {
      // виконується асинхронна функція
      const valeur = await new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом 10 секунд
        setTimeout(() => {
          // успіх — повертаємо значення лічильника
          resolve(52)
        }, 10000)
      })
      // змінюємо значення змінної
      this.$store.commit('increment', valeur)
      // журнал
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
  beforeMount() {
    // тільки клієнт
    console.log('[page4 beforeMount]')
  },
  mounted() {
    // тільки клієнт
    console.log('[page4 mounted]')
  }
}
</script>
  • рядок 30: те, що раніше виконувалося у функції [fetch], тепер виконується у методі [beforeCreate]. Використовується пара async (рядок 30) / await (рядок 36) для очікування завершення асинхронної функції;
  • рядок 36: отримуємо результат асинхронної функції, який повертається у рядку 41 через 10 секунд (рядок 42);
  • рядки 50–54: у методі [created], що виконується як на стороні сервера, так і на стороні клієнта, лічильник присвоюється властивості [value] сторінки;

5.5.3. Виконання

Виконуємо проект [nuxt-02] і вручну вводимо [localhost:81/nuxt-02/page4], щоб звернутися до сервера. Як і під час запуску сторінки [index]:

  • сервер виконує сторінку [page4.vue];
  • надсилає згенеровану сторінку до браузера. Вона відображається;
  • вбудовані в надіслану сторінку клієнтські скрипти беруть контроль і знову виконують сторінку [page4.vue];
  • відображена сторінка при цьому змінюється;

Кінцевий результат такий:

Image

На відміну від очікуваного, значення, що відображається в [2], не дорівнює 52. Що сталося?

Журнали мають такий вигляд:

Image

Можна помітити, що в [1] не відобразився запис про завершення асинхронної дії. Функція [created], яка виводить значення лічильника, показує 0. Все це дозволяє припустити, що [nuxt] не дочекався завершення асинхронної дії.

Якщо повернутися до терміналу VSCode, який використовувався для запуску додатка, можна знайти журнали [3-4]. Видно, що асинхронна функція була успішно виконана на стороні сервера.

У підсумку функція [beforeCreate] була повністю виконана на стороні сервера, але [nuxt] не дочекалася завершення її виконання, щоб надіслати сторінку до браузера клієнта, хоча вона дочекалася завершення функції [fetch]. Отже, саме цей метод слід використовувати, якщо потрібно, щоб сервер ініціалізував магазин [Vuex].

5.6. Навігація в додатку [vue]

Ми показали, що відбувається, коли кожна зі сторінок [index, page1, page2, page3, page4] завантажується сервером спочатку. На практиці все відбувається інакше: під час нормальної роботи на сервері шукається лише сторінка [index]. Розглянемо ці три сторінки в цьому випадку:

сторінка [index]

Image

Ми вже пояснювали цей результат у розділі «Посилання».

Тепер натиснімо на посилання [Page 1]:

Image

Відображене значення дорівнює 0. Воно дорівнювало 25, коли сторінка була вперше запрошена на сервер шляхом ручного введення її адреси URL. Пояснення просте. Виконується такий код:


  created() {
    // клієнт і сервер
    console.log('[page1 created]')
    // тільки сервер
    if (process.server) {
      this.$store.commit('increment', 25)
    }
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('value=', this.value)
},

Саме рядок 6 встановлював лічильник на 25. Оскільки сторінка не була запитана у сервера, рядки 5–7 не були виконані, і лічильник у регістрі [Vuex] залишився на 0.

Тепер натиснімо на посилання [Page 2]:

Image

Цього разу значення не відображається, а в журналах консолі з’являється попередження:

Image

  • У [1] ми бачимо, що функція [asyncData] була виконана клієнтом. Це завжди так:
    • вона виконується сервером, якщо сторінка запитується на сервері; у цьому випадку вона не виконується клієнтом;
    • а потім щоразу, коли сторінка є ціллю поточного маршруту клієнта;
  • у [2]: [nuxt] видає попередження, оскільки в шаблоні сторінки є реактивний вираз {{ value }}, тоді як сторінка не має властивості [value];

Нагадаємо код, що виконується клієнтом:


asyncData() {
    // хто виконує цей код?
    console.log('asyncData, client=', process.client, 'serveur=', process.server)
    // тільки для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут має бути асинхронна функція — але тут її немає
        // цей результат буде включено до властивостей [data]
        resolve({ value: 87 })
      })
    }
  },
  • у рядку 3 видно, що функція [asyncData] була виконана клієнтом ще до функцій життєвого циклу;
  • рядок 5 заблокував виконання решти коду, який створював властивість [value], оскільки цей код виконується клієнтом;

Тепер перейдемо до сторінки [page3]:

Image

  • у [2] ми мали значення 28, коли сторінка була надана сервером. Тут це не так;
  • на сторінці [4] бачимо, що функція [fetch] була виконана на стороні клієнта;

Розглянемо код, виконаний клієнтом:


...
fetch(context) {
    // хто виконує цей код?
    console.log('fetch, client=', process.client, 'serveur=', process.server)
    // лише для сервера
    if (process.server) {
      // повертається обіцянка
      return new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують за допомогою очікування протягом однієї секунди
        setTimeout(() => {
          // успіх
          resolve()
        }, 1000)
      }).then(() => {
        // змінюємо значення в змінній
        context.store.commit('increment', 28)
        // журнал
        console.log('fetch commit terminé')
      })
    }
},
...
  • клієнт виконує метод [fetch], рядок 2;
  • рядки 6–21 не виконуються, оскільки умова [process.server] є хибною. Отже, рядок 17, який встановлює лічильник на 28, не виконується. Він залишається на нулі. Саме тому клієнт відображає 0 замість 28;

Тепер перейдемо до сторінки [page4]. Отримуємо такий результат:

Image

  • у [2] — значення лічильника;
  • у [3] — журнали клієнта;

Код, що виконується клієнтом, такий:


...
data() {
    return {
      value: 0
    }
  },
  // життєвий цикл
  async beforeCreate() {
    // клієнт і сервер
    console.log('[page4 beforeCreate]')
    // тільки для сервера
    if (process.server) {
      // виконується асинхронна функція
      const valeur = await new Promise(function(resolve, reject) {
        // зазвичай тут використовується асинхронна функція
        // її імітують із затримкою в 10 секунд
        setTimeout(() => {
          // успіх — повертаємо значення лічильника
          resolve(52)
        }, 10000)
      })
      // змінюємо значення змінної
      this.$store.commit('increment', valeur)
      // журнал
      console.log('[page4 beforeCreate], fonction asynchrone terminée, compteur=', this.$store.state.counter)
    }
  },
  created() {
    // клієнт і сервер
    this.value = this.$store.state.counter
    console.log('[page4 created], value=', this.value)
  },
...
  • рядки 12–26 не виконуються клієнтом, оскільки умова [process.server] у рядку 12 є хибною. Отже, лічильник у регістрі [Vuex] дорівнює 0 (його початкове значення в регістрі), і саме це значення відображається на сторінці;

Можна запитати, що відбувається, коли закомментувати [if] у рядках 12 та 26. Ось відповідь:

  • цього разу клієнт виконує рядки 14–25, але [nuxt] не чекає на завершення асинхронної функції (як у випадку з сервером) і, отже, залишає лічильник на 0;
  • через 10 с асинхронна функція завершується, і лічильник встановлюється на 52 у рядку 23;
  • коли знову переходимо на сторінку [page4], відображається саме значення 52;

5.7. Résumé

З наших різних випробувань слід винести такі висновки:

  • якщо сторінка [index] має містити зовнішні дані, сервер може їх отримати за допомогою функції [asyncData];
  • якщо сервер повинен ініціалізувати магазин [Vuex] зовнішніми даними під час завантаження сторінки [index], він зробить це у функції [fetch];
  • сторінка, згенерована сервером, та сторінка, згенерована клієнтом, мають бути ідентичними, щоб уникнути ефекту «стрибка», спричиненого тим, що сторінка клієнта візуально замінює сторінку, надіслану сервером і спочатку відображену;

Ми розглянемо інші аспекти функції [nuxt] на новому прикладі.