Начало работы с Axios в Nuxt
Опубликовано: 2022-03-10ayncData
и fetch
для получения данных на стороне сервера с помощью Axios, и узнаем о различиях между этими двумя методами. Наконец, мы узнаем, как добавить аутентификацию в наше приложение с помощью модуля Auth.Nuxt.js предоставляет модуль Axios для простой интеграции с вашим приложением. Axios — это HTTP-клиент на основе обещаний, который работает в браузере и среде Node.js, или, проще говоря, это инструмент для выполнения запросов (например, вызовов API) в клиентских приложениях и среде Node.js.
В этом руководстве мы узнаем, как использовать модуль Axios и как сделать запрос на стороне сервера, используя asyncData и fetch. Эти два метода делают запрос на стороне сервера, но у них есть некоторые отличия, которые мы также рассмотрим. Наконец, мы узнаем, как выполнять аутентификацию и защищать страницы/маршруты с помощью модуля аутентификации и промежуточного программного обеспечения аутентификации.
Эта статья требует базовых знаний о Nuxtjs и Vuejs, так как мы будем опираться на них. Тем, у кого нет опыта работы с Vuejs, я рекомендую начать с их официальной документации и официальной страницы Nuxt, прежде чем продолжить эту статью.
Что такое модуль Nuxt.js Axios?
Согласно официальной документации,
«Это безопасная и простая интеграция Axios с Nuxt.js».
Вот некоторые из его особенностей:
- Автоматически устанавливать базовый URL-адрес для клиентской и серверной части.
- Заголовки прокси-запросов в SSR (полезно для аутентификации).
- Запросы Fetch Style.
- Интегрирован с индикатором прогресса Nuxt.js при выполнении запросов.
Чтобы использовать модуль axios в своем приложении, вам нужно сначала установить его с помощью npm
или yarn
.
ПРЯЖА
yarn add @nuxtjs/axios
НПМ
npm install @nuxtjs/axios
Добавьте его в свой файл nuxt.config.js
:
modules: [ '@nuxtjs/axios', ], axios: { // extra config eg // BaseURL: 'https://link-to-API' }
Массив modules
принимает список модулей Nuxt.js, таких как dotenv, auth и, в данном случае, Axios. Что мы сделали, так это сообщили нашему приложению, что мы будем использовать модуль Axios, на который мы ссылаемся, используя @nuxtjs/axios
. Затем следует свойство axios
, которое является объектом конфигураций, таких как baseURL, как на стороне клиента, так и на стороне сервера.
Теперь вы можете получить доступ к Axios из любого места вашего приложения, вызвав this.$axios.method
или this.$axios.$method
. Где метод можно get
, post
или delete
.
Создание вашего первого запроса с помощью Axios
Для этого урока я собрал простое приложение на Github. Репозиторий содержит две папки, начальную и конечную, начальная папка содержит все, что вам нужно, чтобы сразу приступить к обучению. Папка Finish содержит завершенную версию того, что мы будем строить.
После клонирования репозитория и открытия start
папки нам нужно будет установить все наши пакеты в файл package.json
, поэтому откройте терминал и выполните следующую команду:
npm install
Как только это будет сделано, мы можем запустить наше приложение с помощью команды npm run dev
. Это то, что вы должны увидеть, когда перейдете на localhost:3000
.

Следующее, что нам нужно сделать, это создать файл .env
в корневой папке нашего приложения и добавить в него URL-адрес нашего API. В этом руководстве мы будем использовать пример API, созданный для сбора отчетов от пользователей.
API_URL=https://ireporter-endpoint.herokuapp.com/api/v2/
Таким образом, нам не нужно жестко кодировать наш API в нашем приложении, что полезно для работы с двумя API (разработка и производство).
Следующим шагом будет открытие нашего файла nuxt.config.js
и добавление переменной окружения в нашу конфигурацию axios, которую мы добавили выше.
/* ** Axios module configuration */ axios: { // See https://github.com/nuxt-community/axios-module#options baseURL: process.env.API_URL, },
Здесь мы указываем Nuxt.js использовать этот baseURL
как для клиентских, так и для серверных запросов всякий раз, когда мы используем этот модуль Axios.
Теперь, чтобы получить список отчетов, давайте откроем файл index.vue
и добавим следующий метод в раздел сценария.
async getIncidents() { let res = await this.$store.dispatch("getIncidents"); this.incidents = res.data.data.incidents; }
Что мы сделали, так это создали асинхронную функцию, которую мы вызываем getIncidents()
, и мы можем сказать, что она делает, по имени — она извлекает список инцидентов, используя метод действия хранилища Vuex this.$store.dispatch
. Мы назначаем ответ от этого действия нашему свойству инцидентов, чтобы мы могли использовать его в компоненте.
Мы хотим вызывать метод getIncidents()
всякий раз, когда компонент монтируется. Мы можем сделать это, используя mounted
крюк.
mounted() { this.getIncidents() }
mounted()
— это хук жизненного цикла, который вызывается при монтировании компонента. Это приведет к вызову API при монтировании компонента. Теперь давайте перейдем к нашему файлу index.js
в нашем магазине и создадим это действие, из которого мы будем делать наш запрос Axios.
export const actions = { async getIncidents() { let res = await this.$axios.get('/incidents') return res; } }
Здесь мы создали действие под названием getIncidents
, которое является асинхронной функцией, затем мы ожидаем ответа от сервера и возвращаем этот ответ. Ответ от этого действия отправляется обратно в наш getIncidents()
в нашем файле index.vue
.
Если мы обновим наше приложение, то теперь сможем увидеть длинный список инцидентов, отображаемых на странице.

Мы сделали наш первый запрос, используя Axios, но мы не будем останавливаться на достигнутом, мы собираемся попробовать asyncData
и fetch
, чтобы увидеть различия между ними и использованием Axios.
АсинкДата
AsyncData извлекает данные на стороне сервера и вызывается перед загрузкой компонента страницы. У него нет доступа к this
, потому что он вызывается до того, как будут созданы данные компонента вашей страницы. this
доступно только после вызова created
хука, поэтому Nuxt.js автоматически объединяет возвращенные данные с данными компонента.
Использование asyncData
полезно для SEO, поскольку оно извлекает содержимое вашего сайта на стороне сервера, а также помогает быстрее загружать контент. Обратите внимание, что метод asyncData
можно использовать только в папке страниц вашего приложения, поскольку он не будет работать в папке компонентов. Это связано с тем, что хук asyncData
до создания вашего компонента.

Давайте добавим asyncData
в наш файл index.vue
и посмотрим, как быстро загружаются данные об инцидентах . Добавьте следующий код после нашего свойства компонентов и давайте избавимся от нашего смонтированного хука.
async asyncData({ $axios }) { let { data } = await $axios.get("/incidents"); return { incidents: data.data.incidents }; }, // mounted() { // this.getIncidents(); // },
Здесь метод asyncData
принимает свойство из контекста $axios
. Мы используем это свойство для получения списка инцидентов, после чего возвращается значение. Это значение автоматически внедряется в наш компонент. Теперь вы можете заметить, как быстро загружается ваш контент, если вы обновляете страницу, и в любой момент не возникает никаких инцидентов для отображения.
Принести
Метод Fetch также используется для выполнения запросов на стороне сервера. Он вызывается после созданного хука в жизненном цикле, что означает, что он имеет доступ к данным компонента. В отличие от метода asyncData
, метод fetch можно использовать во всех файлах .vue и с хранилищем Vuex. Это означает, что если у вас есть следующее в вашей функции данных.
data() { return { incidents: [], id: 5, gender: 'male' }; }
Вы можете легко изменить идентификатор или пол , вызвав this.id
или this.gender
.
Использование Axios в качестве плагина
В процессе разработки с Axios вы можете обнаружить, что вам нужны дополнительные настройки, такие как создание экземпляров и перехватчиков для вашего запроса, чтобы ваше приложение могло работать должным образом, и, к счастью, мы можем сделать это, расширив наш Axios в виде плагина.
Чтобы расширить axios
, вам нужно создать плагин (например , axios.js ) в папке plugins
.
export default function ({ $axios, store, redirect }) { $axios.onError(error => { if (error.response && error.response.status === 500) { redirect('/login') } }) $axios.interceptors.response.use( response => { if (response.status === 200) { if (response.request.responseURL && response.request.responseURL.includes('login')) { store.dispatch("setUser", response); } } return response } ) }
Это пример плагина, который я написал для приложения Nuxt. Здесь ваша функция принимает объект контекста $axios
, store
и redirect
, который мы будем использовать при настройке плагина. Первое, что мы делаем, — прослушиваем ошибку со статусом 500
с помощью $axios.onError
и перенаправляем пользователя на страницу входа.
У нас также есть перехватчик, который перехватывает каждый ответ на запрос, который мы делаем в нашем приложении, и проверяет, равен ли статус ответа, который мы получаем, 200
. Если это так, мы продолжаем и проверяем наличие response.request.responseURL
и включает ли он логин. Если это подтверждается, мы отправляем этот ответ, используя метод отправки нашего магазина, где он затем видоизменяется в нашем состоянии.
Добавьте этот плагин в свой файл nuxt.config.js
:
plugins: [ '~/plugins/axios' ]
После этого ваш плагин Axios перехватит любой ваш запрос и проверит, определили ли вы для него особый случай.
Введение в модуль аутентификации
Модуль auth используется для выполнения аутентификации для вашего приложения Nuxt, и к нему можно получить доступ из любого места вашего приложения с помощью $this.auth
. Он также доступен в fetch
, asyncData
, middleware
и NuxtInitServer
из объекта контекста как $auth
.
context
предоставляет дополнительные объекты/параметры от Nuxt до компонентов Vue и доступен в специальных областях жизненного цикла nuxt, подобных упомянутым выше.
Чтобы использовать модуль аутентификации в вашем приложении, вам нужно установить его с помощью yarn
или npm
.
ПРЯЖА
yarn add @nuxtjs/auth
НПМ
npm install @nuxtjs/auth
Добавьте его в свой файл nuxt.config.js
.
modules: [ '@nuxtjs/auth' ], auth: { // Options }
Свойство auth принимает список свойств, таких как strategies
и redirect
. Здесь strategies
принимают предпочитаемый вами метод аутентификации, который может быть:
-
local
Для потока на основе имени пользователя/электронной почты и пароля. -
facebook
За использование учетных записей Facebook в качестве средства аутентификации. -
Github
Для аутентификации пользователей с учетными записями Github. -
Google
Для аутентификации пользователей с учетными записями Google. - Auth0
- Ларавель паспорт
Свойство redirect принимает объект ссылок для:
-
login
Пользователи будут перенаправлены на эту ссылку, если потребуется авторизация. -
logout
Пользователи будут перенаправлены сюда, если после выхода из системы текущий маршрут будет защищен. -
home
Пользователи будут перенаправлены сюда после входа в систему.
Теперь давайте добавим следующее в наш файл nuxt.config.js
.
/* ** Auth module configuration */ auth: { redirect: { login: '/login', logout: '/', home: '/my-reports' }, strategies: { local: { endpoints: { login: { url: "/user/login", method: "post", propertyName: "data.token", }, logout: false, user: false, }, tokenType: '', tokenName: 'x-auth', autoFetchUser: false }, }, }
Обратите внимание, что метод auth
работает лучше всего, когда в приведенном выше варианте указана конечная точка user
.
Внутри объекта конфигурации auth
у нас есть параметр redirect
, в котором мы устанавливаем наш маршрут входа в /login
, маршрут выхода в /
и домашний маршрут в /my-reports
, которые будут вести себя так, как ожидалось. У нас также есть свойство tokenType
, которое представляет тип авторизации в заголовке нашего запроса Axios. По умолчанию установлено значение Bearer
, и его можно изменить для работы с вашим API.
Для нашего API нет типа токена, поэтому мы собираемся оставить его пустой строкой. tokenName
представляет имя авторизации (или свойство заголовка, к которому вы хотите прикрепить свой токен) внутри вашего заголовка в запросе Axios.
По умолчанию установлено значение Authorization
, но для нашего API имя авторизации — x-auth
. Свойство autoFetchUser
используется для включения объекта выборки пользователя с использованием свойства конечной точки user
после входа в систему. По умолчанию это true
, но у нашего API нет конечной точки user
, поэтому мы установили для нее значение false
.
В этом уроке мы будем использовать локальную стратегию. В наших стратегиях у нас есть локальная опция с конечными точками для входа в систему, пользователя и выхода из системы, но в нашем случае мы будем использовать только опцию *login*
, потому что наш демонстрационный API не имеет конечной точки *logout*
и наш пользовательский объект возвращается. когда *login*
успешен.
Примечание. Модуль auth
не имеет опции регистрации конечной точки, поэтому мы собираемся зарегистрироваться традиционным способом и перенаправить пользователя на страницу входа, где мы выполним аутентификацию с помощью this.$auth.loginWith
. Это метод, используемый для аутентификации ваших пользователей. Он принимает «стратегию» (например, local
) в качестве первого аргумента, а затем объект для выполнения этой аутентификации. Взгляните на следующий пример.

let data { email: '[email protected]', password: '123456' } this.$auth.loginWith('local', { data })
Использование модуля аутентификации
Теперь, когда мы настроили наш модуль авторизации, мы можем перейти на нашу страницу регистрации. Если вы посетите страницу /register
, вы должны увидеть регистрационную форму.

Давайте сделаем эту форму функциональной, добавив следующий код:
methods: { async registerUser() { this.loading = true; let data = this.register; try { await this.$axios.post("/user/create", data); this.$router.push("/login"); this.loading = false; this.$notify({ group: "success", title: "Success!", text: "Account created successfully" }); } catch (error) { this.loading = false; this.$notify({ group: "error", title: "Error!", text: error.response ? error.response.data.error : "Sorry an error occured, check your internet" }); } } }
Здесь у нас есть асинхронная функция под названием registerUser
, которая привязана к событию щелчка в нашем шаблоне и отправляет запрос Axios, заключенный в блок try/catch, к конечной точке /user/create
. Это перенаправляет на страницу /login
и уведомляет пользователя об успешной регистрации. У нас также есть блок catch, который предупреждает пользователя о любой ошибке, если запрос не выполнен.
Если регистрация прошла успешно, вы будете перенаправлены на страницу авторизации.

Здесь мы собираемся использовать метод проверки подлинности this.$auth.loginWith('local', loginData)
после чего мы будем использовать this.$auth.setUser(userObj)
auth
установки пользователя в нашем экземпляре проверки подлинности.
Чтобы заставить страницу входа работать, давайте добавим следующий код в наш файл login.vue
.
methods: { async logIn() { let data = this.login; this.loading = true; try { let res = await this.$auth.loginWith("local", { data }); this.loading = false; let user = res.data.data.user; this.$auth.setUser(user); this.$notify({ group: "success", title: "Success!", text: "Welcome!" }); } catch (error) { this.loading = false; this.$notify({ group: "error", title: "Error!", text: error.response ? error.response.data.error : "Sorry an error occured, check your internet" }); } } }
Мы создали асинхронную функцию с именем logIn
используя метод аутентификации this.$auth.loginWith('local, loginData)
. Если эта попытка входа в систему успешна, мы затем назначаем данные пользователя нашему экземпляру аутентификации, используя this.$auth.setUser(userInfo)
и перенаправляем пользователя на страницу /my-report
.
Теперь вы можете получить пользовательские данные, используя this.$auth.user
или с помощью Vuex, используя this.$store.state.auth.user
, но это еще не все. Экземпляр auth
содержит некоторые другие свойства, которые вы можете увидеть, войдя в систему или проверив свое состояние с помощью инструментов разработки Vue.
Если вы зарегистрируете this.$store.state.auth
в консоли, вы увидите следующее:
{ "auth": { "user": { "id": "d7a5efdf-0c29-48aa-9255-be818301d602", "email": "[email protected]", "lastName": "Xo", "firstName": "Tm", "othernames": null, "isAdmin": false, "phoneNumber": null, "username": null }, "loggedIn": true, "strategy": "local", "busy": false } }
Экземпляр auth
содержит свойство loggedIn
, которое полезно при переключении между аутентифицированными ссылками в разделе навигации/заголовка вашего приложения. Он также содержит метод стратегии, в котором указывается тип стратегии, используемой экземпляром (например, локальная).
Теперь мы будем использовать это свойство loggedIn
для организации наших nav
ссылок. Обновите компонент navBar
следующим образом:
<template> <header class="header"> <div class="logo"> <nuxt-link to="/"> <Logo /> </nuxt-link> </div> <nav class="nav"> <div class="nav__user" v-if="auth.loggedIn"> <p>{{ auth.user.email }}</p> <button class="nav__link nav__link--long"> <nuxt-link to="/report-incident">Report incident</nuxt-link> </button> <button class="nav__link nav__link--long"> <nuxt-link to="/my-reports">My Reports</nuxt-link> </button> <button class="nav__link" @click.prevent="logOut">Log out</button> </div> <button class="nav__link" v-if="!auth.loggedIn"> <nuxt-link to="/login">Login</nuxt-link> </button> <button class="nav__link" v-if="!auth.loggedIn"> <nuxt-link to="/register">Register</nuxt-link> </button> </nav> </header> </template> <script> import { mapState } from "vuex"; import Logo from "@/components/Logo"; export default { name: "nav-bar", data() { return {}; }, computed: { ...mapState(["auth"]) }, methods: { logOut() { this.$store.dispatch("logOut"); this.$router.push("/login"); } }, components: { Logo } }; </script> <style></style>
В нашем разделе шаблонов у нас есть несколько ссылок на разные части приложения, в которых мы сейчас используем auth.loggedIn
для отображения соответствующих ссылок в зависимости от статуса аутентификации. У нас есть кнопка выхода из системы, которая имеет событие click
с прикрепленной к ней функцией logOut()
. Мы также отображаем электронную почту пользователя, полученную из свойства auth, доступ к которому осуществляется из нашего хранилища Vuex с помощью метода mapState
, который сопоставляет нашу аутентификацию состояния с вычисляемым свойством компонента навигации. У нас также есть метод logout
из системы, который вызывает наше действие logOut
и перенаправляет пользователя на страницу login
.
Теперь давайте продолжим и обновим наш магазин, чтобы иметь действие logOut
из системы.
export const actions = { // .... logOut() { this.$auth.logout(); } }
Действие logOut
вызывает метод logout
auth, который очищает пользовательские данные, удаляет токены из localStorage
и устанавливает для loggedIn
значение false
.
Такие маршруты, как /my-reports
и report-incident
, не должны быть видны гостям , но на данном этапе в нашем приложении это не так. У Nuxt нет навигационной защиты, которая может защитить ваши маршруты, но есть промежуточное ПО для аутентификации. Это дает вам свободу создавать собственное промежуточное программное обеспечение, чтобы вы могли настроить его для работы так, как вы хотите.
Его можно установить двумя способами:
- По маршруту.
- Глобально для всего приложения в файле
nuxt.config.js
.
router: { middleware: ['auth'] }
Это промежуточное ПО auth
работает с вашим экземпляром auth
, поэтому вам не нужно создавать файл auth.js
в папке промежуточного ПО.
Давайте теперь добавим это промежуточное ПО в наши файлы my-reports.vue
и report-incident.vue
. Добавьте следующие строки кода в раздел сценария каждого файла.
middleware: 'auth'
Теперь наше приложение будет проверять, имеет ли пользователь, пытающийся получить доступ к этим маршрутам, значение auth.loggedIn
, равное true
. Он перенаправит их на страницу входа, используя нашу опцию перенаправления в нашем файле конфигурации аутентификации — если вы не вошли в систему и попытаетесь посетить /my-report
или report-incident
, вы будете перенаправлены на /login
.
Если вы перейдете к /report-incidents
, это то, что вы должны увидеть.

Эта страница предназначена для добавления инцидентов, но прямо сейчас форма не отправляет инцидент на наш сервер, потому что мы не делаем вызов на сервер, когда пользователь пытается отправить форму. Чтобы решить эту проблему, мы добавим метод reportIncident
, который будет вызываться, когда пользователь нажимает Report . У нас это будет в разделе сценария компонента. Этот метод отправит данные формы на сервер. Обновите файл report-incident.vue
следующим образом:
<template> <section class="report"> <h1 class="report__heading">Report an Incident</h1> <form class="report__form"> <div class="input__container"> <label for="title" class="input__label">Title</label> <input type="text" name="title" v-model="incident.title" class="input__field" required /> </div> <div class="input__container"> <label for="location" class="input__label">Location</label> <input type="text" name="location" v-model="incident.location" required class="input__field" /> </div> <div class="input__container"> <label for="comment" class="input__label">Comment</label> <textarea name="comment" v-model="incident.comment" class="input__area" cols="30" rows="10" required ></textarea> </div> <input type="submit" value="Report" class="input__button" @click.prevent="reportIncident" /> <p class="loading__indicator" v-if="loading">Please wait....</p> </form> </section> </template> <script> export default { name: "report-incident", middleware: "auth", data() { return { loading: false, incident: { type: "red-flag", title: "", location: "", comment: "" } }; }, methods: { async reportIncident() { let data = this.incident; let formData = new FormData(); formData.append("title", data.title); formData.append("type", data.type); formData.append("location", data.location); formData.append("comment", data.comment); this.loading = true; try { let res = await this.$store.dispatch("reportIncident", formData); this.$notify({ group: "success", title: "Success", text: "Incident reported successfully!" }); this.loading = false; this.$router.push("/my-reports"); } catch (error) { this.loading = false; this.$notify({ group: "error", title: "Error!", text: error.response ? error.response.data.error : "Sorry an error occured, check your internet" }); } } } }; </script> <style> </style>
Здесь у нас есть форма с полями ввода для заголовка, местоположения и комментария с двусторонней привязкой данных с использованием v-model
. У нас также есть кнопка submit
с событием клика. В разделе сценария у нас есть метод reportIncident
, который собирает всю информацию, предоставленную в форме, и отправляет ее на наш сервер с помощью FormData, поскольку API предназначен также для приема изображений и видео.
Эти formData
прикрепляются к действию Vuex с использованием метода отправки. Если запрос выполнен успешно, вы будете перенаправлены в /my-reports
с уведомлением о том, что этот запрос был выполнен успешно, в противном случае вы будете уведомлены об ошибке с сообщением об ошибке. .
На данный момент в нашем магазине еще нет действия reportIncident
, поэтому в консоли вашего браузера вы увидите сообщение об ошибке, если попытаетесь нажать «Отправить» на этой странице.
![сообщение об ошибке, которое гласит: «[Vuex] неизвестный тип действия: reportIncident»](/uploads/article/1963/SEKFXM60p8RtNYQ0.png)
Чтобы исправить это, добавьте действие reportIncident в файл index.js
.
export const actions = { // ... async reportIncident({}, data) { let res = await this.$axios.post('/incident/create', data) return res; } }
Здесь у нас есть функция reportIncident
, которая принимает пустой объект контекста и данные, которые мы отправляем из нашей формы. Затем эти данные прикрепляются к post
запросу, который создает инцидент и возвращается обратно в наш файл report-incident.vue
.
На этом этапе вы сможете добавить отчет, используя форму, после чего вы будете перенаправлены на страницу /my-reports
.

На этой странице должен отображаться список инцидентов, созданных пользователем, но сейчас он показывает только то, что мы видим выше, давайте исправим это.
Мы собираемся использовать метод fetch
, о котором мы узнали, чтобы получить этот список. Обновите файл my-reports.vue
следующим образом:
<script> import incidentCard from "@/components/incidentCard.vue"; export default { middleware: "auth", name: "my-reports", data() { return { incidents: [] }; }, components: { incidentCard }, async fetch() { let { data } = await this.$axios.get("/user/incidents"); this.incidents = data.data; } }; </script>
Здесь мы используем метод fetch
, чтобы получить пользовательские инциденты и назначить ответ нашему массиву инцидентов.
Если вы обновите свою страницу после добавления инцидента, вы должны увидеть что-то вроде этого.

На этом этапе мы заметим разницу в том, как метод fetch
и asyncData
загружают наши данные.
Заключение
На данный момент мы узнали о модуле Axios и всех его возможностях. Мы также узнали больше об asyncData и о том, как мы можем получить их оба вместе, несмотря на их различия. Мы также узнали, как выполнять аутентификацию в нашем приложении с помощью модуля auth и как использовать промежуточное ПО аутентификации для защиты наших маршрутов. Вот несколько полезных ресурсов, которые рассказывают больше обо всем, что мы рассмотрели.
- Начало работы с метатегами в Nuxjs.
- Используя модуль dotenv в Nuxt.
- Использование Fetch в вашем приложении Nuxt.
- Начало работы с асинхронными данными.
Ресурсы
- «Модуль авторизации», NuxtJS.org
- «Модуль Axios: введение», NuxtJS.org
-
FormData
, веб-документы MDN - «API: метод
asyncData
», NuxtJS.org - «Экземпляр Vue: схема жизненного цикла», VueJS.org
- «Понимание того, как работает
fetch
в Nuxt 2.12», NuxtJS.org