Статья ориентированна на людей, которые уже имеют понимание работы Vue, на котором основан Nuxt, поэтому я буду заострять внимание только на специфических для Nuxt вещах. Но даже если вы не знакомы с ними, то статья даст общее представление как выглядит проект с Nuxt. Вы сможете почерпнуть полезные хаки, плагины и способы решения проблем, которые часто возникают при создании Nuxt приложений. В этой статье я хочу поделиться как создать примитивный интернет-магазин: Для упрощения восприятия процесса в этой статье не будет разбираться создание backend api, так как эта тема довольно объёмная и тянет на отдельную статью. Преимущество прогрессивных (PWA) фреймворков вроде Nuxt.js в том, что:  И так начнем. Я буду использовать Node v12.16.1 и Yarn v1.22.0.
Создаем папку, открываем её вставляем package.json такого содержания и прописываем в консоли yarn install devDependencies не обязательны, но полезны для автоматического форматирования кода и линтинга. Теперь по порядку о каждом пакете: Мы должны задать дефолтную страницу и layout, чтобы для начала хотя бы отрендерить корень сайта. В layouts создаём файл, который будет шаблоном по умолчанию, если явно не задан другой.  Пока что в теге template мы имеем слот в который Nuxt будет рендерить контент каждой страницы с layout по умолчанию. Позже мы вернёмся и добавим туда шапку и футер. У нас есть одно computed свойство meta(), мы его создали на будущее для того, чтобы можно было задать некоторые мета-теги по умолчанию в зависимости от условий.
В этом свойстве можно обратиться в глобальное хранилище Vuex и например взять информацию о покупателе и его устройстве и на основе этой информации задать метатеги для всех страниц.  Далее свойство head(), которое вызывает Nuxt у всех страниц и шаблонов. В него мы передаём метатеги, а так же задаём канонически ссылки для сайта. В данном случае не будем мудрить, и вернем просто объект. Который Nuxt отрендерит в это: Где: data-n-head="ssr" Служебный атрибут на который можно не обращать внимание. data-hid="description" Ранее, в объекте, мы указали свойство hid: 'description'. Оно не является обязательным, но говорит Nuxt присвоить этому конкретному тегу уникальный id, для того, чтобы позже мы могли на других страницах этот тег менять, обращаясь к нему по id. Это очень полезно так как description в layout задан стандартный и для каждого товара и категории товаров он будет своим уникальным (что нужно для SEO).  link: [{ rel: 'canonical', href: canonical }] Очень полезно задавать каноническую ссылку потому, что по умолчанию все страницы в Nuxt регистронезависимые и "слешнезависимые", а это создаёт ненужные нам дубликаты в индексе поиска.
Этим тегом мы динамически задаём тег canonical для всех страниц использующих стандартный шаблон. Технически это не уберёт возможность обращаться к страницам по-разному.
Например, site.com/product/myProduct, site.com/product/myProduct/, site.com/product/MyProduct/ будут разным адресами одной и той же страницы, но у всех их будет одинаковый canonical, что уберёт дубликаты из индекса поисковика. Для удобства можно вынести эту функцию в отдельный файл, но пока оставим это так для наглядности. Так же вы могли заметить закомментированный тег в script. Эту вещь можно смело раскомментировать во время дебага. Зашли на сайт с телефона и автоматически открылась консоль браузера на телефоне. Очень удобно так как можно не прибегать к более сложным инструментам отладки и экономить себе время. Мы создали шаблон, создадим и корневую страницу. В папке pages создаём index.vue Здесь особо нечего комментировать, выглядит как обычный Vue компонент, который будет отображаться в открытии корня сайта. Позже мы его изменим. Теперь дадим Nuxt инструкцию как готовить наш сайт. В корне проекта создаём файл nuxt.config.js Теперь подробно остановимся на каждом пункте конфига Импортируем 2 пакета, которые позже Webpack будет использовать для обработки и сжатия изображений. Для удобства создаём константу, которую будем использовать в качестве флага на некоторых пунктах конфига. Задаём явно, что мы хотим получить SSR приложение (а не SPA). Используем ES6 синтаксис для динамического расширения объекта. В данном случае !isDev означает, что modern: 'client' свойство объекта будет добавлено только в production. modern: 'client' говорит Nuxt создать 2 бандла, один из которых использует ES6 Modules синтаксис поддерживаемый последними браузерами, а второй Legacy транспилированный через Babel. В html будет по 2 тега на каждый js скрипт вида:  Браузер будет загружать только один из пары. Создаём дефолтный Head, как мы это делали в нашем layout, но в данном случае этот head будет стандартным для всего приложения. Указываем русский язык, дефолтный Title и Description и путь к нашей favicon.ico (которой пока что нет в проекте, но создадим её позже). Явно прописываем что считать корнем проекта при использовании абсолютных путей импорта. Отключаем дефолтный механизм Nuxt, который улучшает восприятие UI в некоторых случаях. Этот механизм подгружает страницу как только ссылка на неё попадает в область видимости окна браузера. То есть ещё до клика на ссылку, страница будет уже загружена и пользователь без задержки откроет ссылку. Но в интернет-магазине будут сотни ссылок и нам не нужно, чтобы каждая из них автоматически подгружалась (если у вас слабый сервер, а у клиентов 2G). Бегущая полоска при загрузке страниц, вверху страницы. Это индикатор загрузки, которому можно задать любой цвет или отключить или вставить свой. Он достаточно умный и срабатывает не только при переходе между страницами но при при ожидании ответов по XHR запросам.  Задаем глобальные стили для всего приложения. В данном случае подключаем стили из пакета normalize, для сброса дефолтных стилей. Подключаем Nuxt модули, которые по сути являются обычными плагинами для Vue, но уже со встроенным механизмом install, который их запускает в нужном месте. Нам не нужно их вручную добавлять во Vue instance, а только прописать для некоторым из них конфиги, о которых будет далее идти речь. Задаем конфиг для модуля nuxt-webfontloader. Указываем какой шрифт хотим загрузить. В данном случае берём открытый шрифт из Google Fonts. Когда все шрифты будут загружены, модуль добавит в html тег, класс wf-active. &display=swap  означает, что после получения html, когда браузер начнет исполнять js код, это модуль динамически добавит css файл с нужными нам шрифтами. Так же этот модуль умеет вызывать хуки на всех этапах загрузки, но они нам не нужны сейчас, поэтому пропишем events: false. Задаем конфиг для модуля @nuxtjs/style-resources. Этот модуль автоматически импортирует указанные файлы стилей для всех компонентов. Обычно используется для подключения глобальных переменных SCSS на весь проект. В данном случае у нас ещё нет файла global-variables.scss, но мы его добавим позже, а модуль не вызовет ошибку, если не найдет файл.  Задаем конфиг для модуля axios. Пока что оставим конфиг пустым. Nuxt из коробки имеет под капотом Web Server, который умеет в http2. Но так как проект сейчас на localhost, а для http2 нужен https, то дабы не городить костыли мы просто не будет включать пока что эту опцию.
compressor это Gzip сжатие, которое по умолчанию отдаёт всё сжатым (html, js, css, статику). Подробнее об этом можно почитать здесь https://www.npmjs.com/package/compression Я лишь добавлю, что у себя в production я эту опцию отключил compressor: false так как там Nuxt используется только для render, а всю статику отдаёт Nginx с настроенным сжатием, поэтому дабы дважды не делать одну и ту же работу, можно это отключить. Но для примера мы будем использовать Nuxt для отдачи статики и не будем трогать эту опцию.
То же самое и с etag, если будет использоваться Nginx, то стоит явно отключить etags.
resourceHints мы явно отключим, чтобы не предзагружать страницы (из той же оперы что и prefetchLinks: false) . Так как позже мы будет использовать cssNano со своими настройками, отключаем дефолтный механизм оптимизации стилей. Nuxt сам подставит в Webpack эту часть конфига. Здесь мы задаем то, что во время development хотим видеть красивые имена файлов, а в production build раскидываем файлы по папкам и в качестве имени используем contenthash. Это элегантное решение распространённой проблемы, когда вы скажем обновили приложение на production, а у клиентов все js скрипты и стили в кеше бразуера. Чтобы инвалидировать этот кеш, мы просто каждый раз генерируем у всех файлов название, которое является хеш-функцией контента внутри этого файла. Соответственно если какой-то файл поменяется, то при следующем заходе пользователя на страницу, браузер будет требовать уже файл с другим именем, а те что не поменялись останутся с тем же именем и будут взяты и с кеша браузера.   Как мы видим все файлы и даже html закеширован браузером, что сводит практически к 0 весь трафик между сервером и браузером. Отключаем для Development всю минификацию html, чтобы ускорить процесс разработки. Разбиваем на независимые чанки всё приложение. Отключаем минификацию js для development. По умолчанию Nuxt автоматически добавляет все стили проекта прямо внутрь html через тег style. Это уменьшает количество запросов к серверу, так как у нас нигде не будет нужно браузеру загружать css файлы отдельно. Но это так же лишает браузер возможности кешировать css стили. Поэтому для development мы включаем inline styles, а в production разбиваем их на чанки для каждого компонента и создаем отдельные файлы.
ignoreOrder: true Нужно задать, чтобы во время сборки нам не выдавал webpack ложные предупреждения о найденных конфликтах и дубликатах, это своеобразное решение бага #4885. По умолчанию Babel старается трансплитирировать весь код проекта, но иногда он не делает это с некоторыми зависимостями и их нужно задать явно.  Nuxt из коробки имеет встроенный Postcss для которого здесь мы задаём конфиг. Здесь мы можем подключать полезные плагины для Postcss, задавать их порядок исполнения. В данном случае мы отключаем для development все плагины, а в production весь css будет минифицирован и к нему будут присвоены vendor префиксы для 99.5% браузеров. Так же мы можем прописать плагины, которые будут использоваться сразу в двух средах. Здесь мы можем изменять webpack конфиг, напрямую перехватывать нужные loaders или test.
По умолчанию Webpack '/\\.(png|jpe?g|gif|svg|webp)$/i' ищет все картинки и svg файлы и обрабатывает их через встроенный url-loader file-loader для того чтобы мы могли в компонентах импортировать эти файлы. Но он никаких их не сжимает и его возможности по работе с svg сильно ограничены. Поэтому мы изменим это поведение. Создадим константы в которых будем хранить новые загрузчики и плагины Webpack, а так же строку для поиска встроенного загрузчика, чтобы позже её перехватить. Добавляем плагин для сжатия только в production Перехватываем стандартный загрузчик и убираем из него svg Добавляем наш загрузчик для svg На этом настройка nuxt.config.js завершена и мы можем запустить проект. Для начала запустим Nuxt в режиме development. Для этого пропишем в консоли yarn dev
Заходим в браузере по адресу http://localhost:3000/ и видим, что всё хорошо.  Теперь запустим production сервер, отключаем dev server с помощью Ctrl+C в консоли пишем yarn buildandstart Сервер будет доступен по тому же порту, но его можно изменить в package.json. Давайте проверим скорость.  Попробуем разобраться почему выдаёт 99/100 Из всего этого следует, что если немного подшаманить то без особых сложностей мы получим вот это, но это уже отдельная история с подготовкой сервера к HighLoad и тд.  Так как мы не будем использовать API, а будем эмулировать работу с ним, то для примера инкапсулируем всю логику запросов на сервер через Vuex. Для начала создадим в корне папку store и файл index.js с таким содержанием Если вы не знакомы с Vuex под спойлером будет подробное описание файла Функция, которая позволит нам симулировать задержку с сервера внутри Async функций. Это наше глобальное состояние приложения. В нём мы прописываем дефолтные значения переменных. Через функции, которые называются мутациями мы можем изменить состояние. Не рекомендуется менять его напрямую. Я намеренно опустил этап валидации и тд. поэтому всё сводится просто к присвоению переменной. Здесь будут наши actions, это тоже функции, но они не меняют напрямую состояние, но зато получают в качестве первого аргумента объект store из которого в данном случае нам нужна функция comit, которая получает в качестве первого аргумента имя мутации, а второго данные, которые она передаёт в мутацию. А в качестве второго аргумента action получает контекст Nuxt где мы можем обращаться к разным плагинам, например к тому же axios и работать через него с реальным API.
По сути в actions будет проходить вся работа с API. Мы можем также вызывать другие actions, таким образом создавая цепочки. В данном случае мы симулируем работу с API, но в будущем можем заменить её на реальный API при этом на не придется переписывать код компонентов и страниц так как вся логика работы скрыта.  В папке components создаём папку commons, в которой мы будем хранить общие компоненты. Далее создаём файл CategoriesList.vue с таким содержанием: Для примера будем использовать относительно новую функцию CSS Modules, которая идёт из коробки и превращает например класс wrapper в wrapper_26mY3 , что позволяет вам не задумывать об именовании локальных классов в стилях компонентов.
К классам придётся обращаться через объект $style в данном случае. Компонент будет получать данные из родительского, это позволит сделать его dump (глупым) и уменьшает сложность приложения. То есть он не работает с API или глобальным состоянием. Nuxt-link это встроенная обвёртка вокруг <router-link> , которая берёт на себя переходы между страницами, а так же имеет дополнительные функции, которые мы отключили ранее (предзагрузка страницы, когда эта ссылка появится в области видимости.
Мы передаём в атрибут to строковый литерал с переменной cSlug  в конце, в которой у нас будут храниться уникальные пути к каждой категории. В итоге в случае первой категории это отрендерится в  И хотя в dom это обычный a href, но на нём висят обработчики кликов и IObserver. Поэтому даже если у пользователя отключен JS в браузере, он сможет переходить по ссылкам и получать отрендеренный контент с сервера.  Теперь мы хотим отобразить этот компонент на главной странице сайта.  Импортируем наш список категорий используя ~~, что означает абсолютный путь. Нам нужно получить глобальное состояние в котором храниться список категорий. asyncData  Это специальный метод, который вызывает Nuxt на сервере. В нём мы можем получить данные из API напрямую, но в данном случае, мы хотим их получить вызвав определённый action в Vuex. Так же перед функцией есть приставка async , что позволяет нам сказать Nuxt не рендерить страницу пока данные из API не получены.  Через обвертку получаем в страницу объект categories привязанный к глобальному состоянию categoriesList Выводим этот компонент, передавая ему в props объект categories  При нажатии на категорию nuxt пытается найти маршрут, но он его не найдет. Давайте создадим для каждой категории свой маршрут. В папке pages создаём папку category и в ней файл с именем _CategorySlug.vue такого содержания: Название файла должно начинаться с _, чтобы Nuxt понял, что это динамический маршрут.
Когда мы переходим на страницу http://127.0.0.1:3000/category/cats
Мы можем обратиться к объекту route таким образом route.params.CategorySlug (без нижнего подчеркивания), которое будет равным cats
В этом файле все аналогично index.vue, но есть пару отличий Мы вызываем actions, который мы создадим в следующем шаге, но вторым аргументам передаём вышеупомянутый объект route.  Прописываем для этой страницы Title и Meta description, которые мы получаем из API. getCurrentCategory  в этом action на основании текущего route мы ищем нужную категорию и добавляем её в state.  Теперь наши маршруты работают. Я использовал первый попавшийся генератор JSON с такими настройками.

Полученный файл кладем в static/mock. Nuxt предоставляет публичный доступ к файлам, которые лежат в папке static. Поэтому мы сможем подтянуть наши товары используя Axios. Для начала изменим наш Vuex к такому виду: Здесь мы переделали action getCurrentCategory , который с помощью axios получает файл products.json. После небольшой обработки мы получаем страницу категории со списком всех товаров и ссылками на них. Чтобы их отрендерить создадим компонент ProductBrief.vueв components/category. Это dump компонент, который будет являться карточкой товара. Изменим нашу страницу категории  Теперь она будет рендерить все товары в категории в таком виде.  Всё хорошо, но в категории 150 товаров и это напрягает мой мобильный интернет, который пытается загрузить все картинки. Давайте сделаем загрузку ленивой. Для этого в plugins добавим  В котором мы указываем индикатор загрузки и placeholder в случае, если картинка не доступна на сервере.
Подключаем плагин в nuxt.config.js И меняем наш img srcна Так же добавляем стиль  И вуаля   Анимированый индикатор загрузки и ленивая загрузка. Мой смартфон выдохнул. Спасибо всем кто прочитал статью. Это просто практический пример использования Nuxt. В реальном проекте нужно исходить из бизнес-логики, структуры данных и тд. тп, а этот проект далек от реальности. В продолжении мы будем наращивать функционал сайта, так как пока что он никуда не годится. CEO