<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Self-Hosted</title><generator>teletype.in</generator><description><![CDATA[Мой опыт использования Self-Hosted решений]]></description><link>https://teletype.in/@self-hosted?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=self-hosted</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/self-hosted?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/self-hosted?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Tue, 21 Apr 2026 06:14:20 GMT</pubDate><lastBuildDate>Tue, 21 Apr 2026 06:14:20 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@self-hosted/e-commerce-store-with-nuxt3-and-strapi</guid><link>https://teletype.in/@self-hosted/e-commerce-store-with-nuxt3-and-strapi?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=self-hosted</link><comments>https://teletype.in/@self-hosted/e-commerce-store-with-nuxt3-and-strapi?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=self-hosted#comments</comments><dc:creator>self-hosted</dc:creator><title>Как создать интернет-магазин с Nuxt3 и Strapi</title><pubDate>Sun, 19 Mar 2023 14:29:51 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/a2/ee/a2ee8cf9-b1e2-45bb-8fa0-6b4adcc36251.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/e9/29/e9296576-ad2e-4076-8e9a-f9ccee9718b0.png"></img>В этом руководстве мы собираемся создать интернет-магазин с фронтендом на Nuxt и бэкендом на Strapi, используя официального руководство How to build an E-commerce Store with Nuxt.js and Strapi.]]></description><content:encoded><![CDATA[
  <p id="jVU5">В этом руководстве мы собираемся создать интернет-магазин с фронтендом на Nuxt и бэкендом на <a href="https://strapi.io/" target="_blank">Strapi</a>, используя официального руководство <a href="https://strapi.io/blog/how-to-build-an-e-commerce-store-with-nuxt-js-and-strapi" target="_blank">How to build an E-commerce Store with Nuxt.js and Strapi.</a></p>
  <figure id="g2Il" class="m_column">
    <img src="https://img3.teletype.in/files/e9/29/e9296576-ad2e-4076-8e9a-f9ccee9718b0.png" width="2000" />
  </figure>
  <p id="rN5R">Вам для урока понадобятся:</p>
  <ul id="jCNV">
    <li id="b9m4">Обязательно пройденное руководство по <a href="https://vuejs.org/tutorial/#step-1" target="_blank">vuejs</a> или <a href="https://www.vuemastery.com/courses/intro-to-vue-3/intro-to-vue3" target="_blank">курс</a></li>
    <li id="tAts">Знакомство с <a href="https://nodejs.org/en/docs/guides/getting-started-guide" target="_blank">nodejs</a>(рекомендованная версия v18)</li>
    <li id="W9Zv">Знакомство с фреймворком <a href="https://getbootstrap.com/" target="_blank">Bootstrap</a></li>
  </ul>
  <h2 id="752u">Установка Strapi</h2>
  <p id="tpOd">Из документации основные преимущества Strapi - это гибкость, открытый исходный код, современный Headless подход к системе управления контентом, где вам не потребуется писать много кода при этом эффективно доставлять контент.  Перед локальной установкой вы можете попробовать <a href="https://strapi.io/demo" target="_blank">демонстрационную версию</a>.</p>
  <p id="cV0s">Для установки воспользуемся актуальными требованиями и рекомендация на github&#x60;е , соответственно нам потребуется операционная система Ubuntu и рекомендуется установить nodejs v18.x.</p>
  <h3 id="yMh8">Установка nodejs v18 с помощью nvm</h3>
  <p id="4al8">Переходим на страничку с документацией по установке <a href="https://github.com/nvm-sh/nvm#installing-and-updating" target="_blank">nvm</a> и выполняем скрипт установки предварительно создав пользователя strapi </p>
  <pre id="EEfE">useradd strapi
sudo su - strapi
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
nvm install 18</pre>
  <p id="WosO">Проверяем установку:</p>
  <pre id="vrbQ">node --version
v18.15.0</pre>
  <p id="bpok">Обновляем npm:</p>
  <pre id="jQNJ">npm install -g npm
npm --version
9.6.2</pre>
  <p id="asJO">Устанавливаем yarn и проверяем:</p>
  <pre id="NUKS">npm install -g yarn
yarn  --version
1.22.19</pre>
  <h3 id="bzag">Создаем и запустим strapi приложение</h3>
  <p id="22HN">Ранее мы установили nodejs из под пользователя strapi то и запускать необходимо из-под него.</p>
  <pre id="2MPY">strapi@rock-5b:~$ pwd
/home/strapi
strapi@rock-5b:~$ yarn create strapi-app app --quickstart</pre>
  <p id="UPiP">Видим что приложение запустилось:</p>
  <pre id="WxoC">┌────────────────────┬──────────────────────────────────────────────────┐
│ Time               │ Sun Mar 19 2023 15:14:22 GMT+0500 (Yekaterinbur… │
│ Launched in        │ 6130 ms                                          │
│ Environment        │ development                                      │
│ Process PID        │ 47802                                            │
│ Version            │ 4.8.2 (node v18.15.0)                            │
│ Edition            │ Community                                        │
│ Database           │ sqlite                                           │
└────────────────────┴──────────────────────────────────────────────────┘
 Actions available
One more thing...
Create your first administrator 💻 by going to the administration panel at:
┌─────────────────────────────┐
│ http://localhost:1337/admin │
└─────────────────────────────┘</pre>
  <p id="GyRB">Потребление памяти до 512Мб</p>
  <figure id="TdMl" class="m_retina">
    <img src="https://img3.teletype.in/files/2c/87/2c876b6c-4090-4aba-850c-05ccf5af7177.png" width="1030" />
  </figure>
  <h3 id="AJgb">Создаем пользователя</h3>
  <p id="B2hs">Для создания пользователя открываем страничку <a href="http://localhost:1337/admin" target="_blank">http://localhost:1337/admin</a> с панелью администратора. Так как мы установили strapi на удаленном хосте, то при попытке подключится по адресу отличному от localhost&#x60;а приводит к ошибке:</p>
  <figure id="V56y" class="m_column">
    <img src="https://img2.teletype.in/files/9f/af/9faf0325-0251-43e1-a2f7-b9be80ff7e3e.png" width="1282" />
  </figure>
  <p id="SqEu">Во избежании этого необходимо пробросить порт 1337 с удаленного сервера на localhost:</p>
  <pre id="c6rn">ssh -L 1337:localhost:1337 root@192.168.1.10</pre>
  <figure id="0dmY" class="m_retina">
    <img src="https://img1.teletype.in/files/00/13/00130703-1624-41e0-b457-1ca8d0e42e25.png" width="575" />
    <figcaption>В пароле должно быть не менее 8 символов, 1 прописная, 1 строчная и 1 цифра.</figcaption>
  </figure>
  <h3 id="hSs4">Получаем публичный адрес</h3>
  <p id="yqL4">Для получения публичного адреса <a href="https://api.my-domain.ru" target="_blank">https://api.my-domain.ru</a>, добавляем параметр url в конфиг app/config/server.js:</p>
  <pre id="dXPg">module.exports = ({ env }) =&gt; ({
  host: env(&#x27;HOST&#x27;, &#x27;0.0.0.0&#x27;),
  port: env.int(&#x27;PORT&#x27;, 1337),
  url: &#x27;https://api.my-domain.ru/&#x27;,
  app: {
    keys: env.array(&#x27;APP_KEYS&#x27;),
  },
  webhooks: {
    populateRelations: env.bool(&#x27;WEBHOOKS_POPULATE_RELATIONS&#x27;, false),
  },
});</pre>
  <p id="8-hint">И обязательно пересобираем админский интерфейс</p>
  <pre id="PvIs">yarn strapi build</pre>
  <p id="MVeq">И настраиваем systemd сервис /etc/systemd/system/strapi-app.service: </p>
  <pre id="84xk">[Unit]
Description=Strapi app
After=network.target

[Service]
ExecStart=/home/strapi/.nvm/versions/node/v18.15.0/bin/node /home/strapi/app/node_modules/.bin/strapi start
User=strapi
WorkingDirectory=/home/strapi/app
Environment=NODE_ENV=production
Environment=PATH=/home/strapi/.nvm/versions/node/v18.15.0/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Restart=always

[Install]
WantedBy=multi-user.target</pre>
  <p id="WBi6">Запускаем сервис strapi-app:</p>
  <pre id="FcG3">systemctl daemon-reload
systemctl start strapi-app
systemctl status strapi-app
root@rock-5b:~# systemctl status strapi-app
● strapi-app.service - Strapi app
     Loaded: loaded (/etc/systemd/system/strapi-app.service; disabled; vendor preset: enabled)
     Active: active (running) since Mon 2023-03-20 00:08:33 +05; 10s ago
   Main PID: 53474 (node)
      Tasks: 11 (limit: 18504)
     Memory: 135.3M
        CPU: 4.565s
     CGroup: /system.slice/strapi-app.service
             └─53474 /home/strapi/.nvm/versions/node/v18.15.0/bin/node /home/strapi/app/node_modules/.bin/strapi start

Mar 20 00:08:36 rock-5b node[53474]: │ Version            │ 4.8.2 (node v18.15.0)                            │
Mar 20 00:08:36 rock-5b node[53474]: │ Edition            │ Community                                        │
Mar 20 00:08:36 rock-5b node[53474]: │ Database           │ sqlite                                           │
Mar 20 00:08:36 rock-5b node[53474]: └────────────────────┴──────────────────────────────────────────────────┘
Mar 20 00:08:36 rock-5b node[53474]:  Actions available
Mar 20 00:08:36 rock-5b node[53474]: Welcome back!
Mar 20 00:08:36 rock-5b node[53474]: To manage your project 🚀, go to the administration panel at:
Mar 20 00:08:36 rock-5b node[53474]: https://api.my-domain.ru/admin
Mar 20 00:08:36 rock-5b node[53474]: To access the server ⚡️, go to:
Mar 20 00:08:36 rock-5b node[53474]: https://api.my-domain.ru</pre>
  <h3 id="fZZF">Устанавливаем HAProxy</h3>
  <p id="nkpm">Прокси используется для получения безопасного TLS соединения и получения публичного url <a href="https://api.my-domain.ru" target="_blank">https://api.my-domain.ru</a></p>
  <pre id="T4w5"></pre>
  <p id="o3gI"> Устанавливаем пакет haproxy по <a href="https://docs.strapi.io/dev-docs/deployment/haproxy-proxy" target="_blank">инструкции</a>, но как альтернативу можно рассмотреть Nginx, Caddy, PM2:</p>
  <pre id="6Bt8">apt-get install haproxy</pre>
  <p id="wVfq">Настраиваем бэкенд для strapi добавим кониг ниже /etc/haproxy/haproxy.cfg</p>
  <pre id="04bn">frontend api.example.com
        bind *:80
        default_backend strapi-backend

backend strapi-backend
        server local 127.0.0.1:1337</pre>
  <p id="fMYH">И перезапустим haproxy:</p>
  <pre id="WI0k">systemctl restart haproxy
systemctl status haproxy</pre>
  <h3 id="pvgm">Настраиваем TLS </h3>
  <p id="7Kxi">Устанавливаем пакет cerbot:</p>
  <pre id="oXue">apt-get install certbot</pre>
  <p id="nwwG">Настраиваем бэкенд для certbot в /etc/haproxy/haproxy.cfg:</p>
  <pre id="k1Il">frontend api.my-domain.ru
        bind *:80
        #bind *:443 ssl crt /etc/letsencrypt/api.my-domain.ru.pem
        #http-request redirect scheme https unless { ssl_fc }

        acl letsencrypt-acl path_beg /.well-known/acme-challenge/
        use_backend letsencrypt-backend if letsencrypt-acl
        
backend letsencrypt-backend
        server certbot 127.0.0.1:8899</pre>
  <p id="n8vU">Далее перезапускаем haproxy и запрашиваем сертификат:</p>
  <pre id="ecTQ">systemctl restart haproxy
certbot certonly --standalone -d api.my-domain.ru --non-interactive --agree-tos --http-01-port=8899
cat /etc/letsencrypt/live/api.sh3h.ru/fullchain.pem /etc/letsencrypt/live/api.my-domain.ru/privkey.pem &gt; /etc/letsencrypt/api.my-domain.ru.pem</pre>
  <p id="qtsw">После раскомментируем строчки с настройкой SSL и перезапускаем haproxy снова</p>
  <p id="QeUL">Открываем в браузере публичный url <a href="https://api.my-domain.ru/admin" target="_blank">https://api.my-domain.ru/admin</a> и убеждаемся, что haproxy создает безопасное соединение и панель администратора успешно открывается.</p>
  <figure id="0sPN" class="m_column">
    <img src="https://img3.teletype.in/files/ea/54/ea54baca-1054-4bc9-980e-76ea31442fb5.png" width="1250" />
  </figure>
  <h2 id="hSss">Создаем бэкенд API</h2>
  <p id="spkO">У нас уже запущен и работает Strapi; следующим шагом будет создание content-types нашего приложения.</p>
  <h3 id="fyM8"><strong>1. Создаем collection type для товаров</strong></h3>
  <figure id="Dd62" class="m_column">
    <img src="https://img1.teletype.in/files/4f/93/4f93c34c-daac-4e3e-87c7-0700c5217efd.png" width="1598" />
  </figure>
  <p id="01G9">Создайте следующие поля в разделе product content-type</p>
  <ul id="xvPc">
    <li id="JWog">Name - short text</li>
    <li id="bUq0">Description - short text</li>
  </ul>
  <figure id="VMW4" class="m_column">
    <img src="https://img2.teletype.in/files/18/72/18723eb6-b5b6-4308-887f-fcaeaf5ed76e.png" width="1590" />
  </figure>
  <ul id="en0G">
    <li id="EuPb">Price - decimal number</li>
    <li id="PVcd">Image - multiple media</li>
    <li id="tSRR">Slug - UID</li>
  </ul>
  <figure id="4WUM" class="m_column">
    <img src="https://img3.teletype.in/files/a1/9b/a19b9437-365e-4d09-be2a-e501d236c38e.png" width="1608" />
  </figure>
  <p id="pijo"><strong>2. Создаем collection type для категорий</strong></p>
  <p id="fHNT">Аналогично товарам создаем collection type для категорий товаров с полями:</p>
  <ul id="UcBI">
    <li id="Ak9m">Name - short text</li>
    <li id="PssE">Slug - UID</li>
    <li id="0wFu">Связь - один ко многим товарам</li>
  </ul>
  <figure id="ddTm" class="m_column">
    <img src="https://img4.teletype.in/files/b7/99/b7998ae2-2be6-4cf7-afc2-50dc269c17e6.png" width="1614" />
  </figure>
  <p id="fmQ7"><strong>2. Создаем collection type для заказов</strong></p>
  <p id="7PZq">Аналогично товарам создаем collection type для заказов с полями:</p>
  <ul id="4cjB">
    <li id="TIFD">Item as JSON</li>
  </ul>
  <figure id="rey8" class="m_column">
    <img src="https://img4.teletype.in/files/7a/ec/7aeca68e-e44c-4f3f-a2b9-2d2efa9a6817.png" width="1548" />
  </figure>
  <p id="HPXG"><strong>2. Создаем collection type для клиентов</strong></p>
  <p id="J22W">Аналогично товарам создаем collection type для заказов с полями:</p>
  <ul id="5xDh">
    <li id="JWtt">Name - short text</li>
    <li id="RRFf">Phone - big int</li>
    <li id="gbOO">Email - email</li>
  </ul>
  <figure id="SsRF" class="m_column">
    <img src="https://img3.teletype.in/files/24/0e/240ec9a0-595a-4ec2-a1a3-68ec0c84d1ef.png" width="1564" />
  </figure>
  <p id="qcDd">Сохраняем все collection type и получаем токен к API Settings -&gt; API Tokens-&gt; Create new API Token</p>
  <figure id="v1m4" class="m_column">
    <img src="https://img1.teletype.in/files/41/29/4129ca90-4f60-4a6f-b516-303c07d29762.png" width="2030" />
  </figure>
  <p id="zbj9">Теперь можно проверить нам API в формате JSON:</p>
  <pre id="9yyW">export STRAPI_TOKEN=токен полученный выше
curl -sH &quot;Authorization: bearer $STRAPI_TOKEN&quot; http://api.my-domain.ru/api/products | jq .
{
  &quot;data&quot;: [],
  &quot;meta&quot;: {
    &quot;pagination&quot;: {
      &quot;page&quot;: 1,
      &quot;pageSize&quot;: 25,
      &quot;pageCount&quot;: 0,
      &quot;total&quot;: 0
    }
  }
}</pre>
  <h2 id="aKdm">Устанавливаем Nuxt</h2>
  <p id="SDis">Мы будет использовать Nuxt в качестве фреймворка в режиме <a href="https://nuxtjs.org/docs/concepts/server-side-rendering/" target="_blank">рендеринга на стороне сервера</a> универсальных приложение Vue.js.</p>
  <p id="01H6">Для установки необходимо выполнить команды из-под пользователя strapi:</p>
  <pre id="xCQA">npx nuxi init front
cd front
yarn install</pre>
  <p id="EofW">Запуск сервера разработки фронта:</p>
  <pre id="s0jr">yarn dev -o</pre>
  <p id="VfvT">Наш фронт запустится на порту 3000. Аналогично бэкенду создадим systemd сервис nuxt-front и настроим бэкенд для haproxy:</p>
  <p id="lATl">Далее запускаем сервис и перезагружаем haproxy:</p>
  <pre id="sGhv">systemctl start nuxt-front
systemctl restart haproxy</pre>
  <p id="XPLl">И открываем в браузере наш фронтенд по адресу https://store.my-domain.ru/:</p>
  <figure id="bvzS" class="m_column">
    <img src="https://img1.teletype.in/files/01/f7/01f70258-7ebc-4ef5-8cab-1572136d835b.png" width="1860" />
  </figure>
  <h2 id="cHTr">Создаем  фронтенд интернет-магазина</h2>
  <p id="716j">Перед тем как приступить к создания внешнего интерфейса нашего интернет-магазина необходимо:</p>
  <ol id="1SMh">
    <li id="N54E">Подготовить среду для локальной разработки.</li>
    <ul id="nDOE">
      <li id="6XG2">Установить IDE - это может быть WebStorm. PyCharm, VS Code + Vetur</li>
      <li id="oNUd">Установить git, nodejs v18, yarn, nuxt</li>
      <li id="Qt8F">Создать приватный репозиторий на <a href="https://github.com/" target="_blank">https://github.com/</a></li>
      <li id="ikvM">Настроить деплой на наш удаленный сервер store.my-domain.ru.</li>
    </ul>
    <li id="1tUl">Получить макет сайта для верстки в HTML или воспользоваться готовым шаблоном на bootstrap v4</li>
    <ul id="AWyw">
      <li id="2ab1">Получить табличку с ассортиментом и ценами интернет-магазина</li>
      <li id="82wN">Получить и обработать фотографии для каталога товаров. Картинки должны быть выровнены по высоте и ширине, иметь одинаковое разрешение по высоте и ширине в пикселя в соответствии с максимальным разрешением в макете сайта. Далее необходимо оптимизировать все картинки по размеру используя например инструмент <a href="https://tinypng.com/" target="_blank">https://tinypng.com/</a>.</li>
      <li id="xp67">Загрузить все товары в ручную через strapi бэкенд сайта <a href="https://api.my-domain.ru/" target="_blank">https://api.my-domain.ru/</a>. Так же можно воспользоваться API для импорта большого объема данных из таблички или мигрировать данные из одного из конструкторов сайтов для e-commerce.</li>
    </ul>
  </ol>
  <p id="V7On">В итоге мы должны получить либо готовый шаблон сайта на Bootstrap или вам потребуется создать свой шаблон, что заслуживает написание отдельной статьи, где за основу можно взять статью <a href="https://designmodo.com/create-bootstrap-theme/" target="_blank">How to Create a Custom Bootstrap Theme from Scratch. </a></p>
  <p id="76X0">Для демонстрации возможностей strapi и nuxt в данной статье, возьмем готовый бесплатный <a href="https://echotemplate.com/product/olog-ecommerce-responsive-html-template" target="_blank">шаблон</a> сайта для онлайн коммерции.</p>
  <figure id="Blku" class="m_column">
    <img src="https://img3.teletype.in/files/e2/ef/e2efaabf-2e76-47d2-9d7d-df3f837e29ac.png" width="1900" />
  </figure>
  <h3 id="V5lp">Переносим статику Bootstrap в nuxt</h3>
  <p id="Oql6">После того как распаковали архив с шаблоном сайта раскладываем полученные файлы в проект front:</p>
  <pre id="g54I">olog-ecommerce-responsive-html-template-1.0/dist =&gt; front/public/dist
olog-ecommerce-responsive-html-template-1.0/src/scss/vendors =&gt; front/public/vendors
olog-ecommerce-responsive-html-template-1.0/src/scss =&gt; front/assets/scss</pre>
  <p id="CJ31">Подключаем scss стили в конфиге front/nuxt.config.ts:</p>
  <pre id="A4Y0">export default defineNuxtConfig({
    css: [
        &#x27;@/assets/scss/main.scss&#x27;
    ],
}</pre>
  <p id="GwLt">Воспользуемся IDE и откроем index.html файл шаблона, где свернем все основные секции HTML блоков</p>
  <figure id="lr2e" class="m_column">
    <img src="https://img3.teletype.in/files/ef/f4/eff436be-791f-40ef-a84e-a886b2b6028c.png" width="1470" />
  </figure>
  <p id="RUV0">Перенесем полученные секции в vue компоненты во внутрь тэга &lt;template&gt;:</p>
  <pre id="1FvN">Header Area =&gt; front/components/HeaderArea.vue
Banner Area =&gt; front/components/BannerArea.vue
Features Section =&gt; front/components/FeaturesSection.vue
About Area =&gt; front/components/AboutArea.vue
Populer Product =&gt; front/components/PopulerProduct.vue
Categorys Section =&gt; front/components/CategorysSection.vue
Features Section of customersreview =&gt; front/components/FeaturesSectionCustomer.vue
Footer =&gt; front/components/Footer.vue </pre>
  <figure id="KrJv" class="m_column">
    <img src="https://img3.teletype.in/files/24/04/2404e13e-7b02-4e3d-9f4f-b1dfcfde0400.png" width="1204" />
  </figure>
  <p id="B48k">Используя полученные компоненты <em>HeaderArea</em> и <em>Footer</em> создадим основной слой <em>front/layouts/default.vue</em>:</p>
  <pre id="ei8L">&lt;template&gt;
  &lt;HeaderArea /&gt;
  &lt;main&gt;
    &lt;slot /&gt;
  &lt;/main&gt;
  &lt;Footer /&gt;
&lt;/template&gt;</pre>
  <p id="uyXk">Из оставшихся компонентов создадим главную страничку <em>front/pages/index.vue</em>:</p>
  <pre id="GShV">&lt;template&gt;
  &lt;BannerArea /&gt;
  &lt;FeaturesSection /&gt;
  &lt;AboutArea /&gt;
  &lt;PopulerProduct /&gt;
  &lt;CategorysSection /&gt;
  &lt;FeaturesSectionCustomer /&gt;
&lt;/template&gt;</pre>
  <p id="tIRG">Далее подключим слои и странички приложении f<em>ront/</em>app.vue:</p>
  <pre id="Mz4f">&lt;template&gt;
  &lt;NuxtLayout&gt;
    &lt;NuxtPage /&gt;
  &lt;/NuxtLayout&gt;
&lt;/template&gt;</pre>
  <p id="1Icy">Перед запуском приложение устанавливаем пакет sass:</p>
  <pre id="3E2I"> yarn add --dev sass sass
 yarn dev -o</pre>
  <h3 id="lSyV">Подключаем бэкенд strapi</h3>
  <p id="mmZz">Первым делом необходимо загрузить карточки продуктов и категорий в наш бэкенд</p>
  <figure id="A8Vg" class="m_column">
    <img src="https://img3.teletype.in/files/23/b0/23b0cf59-f965-4aa2-a080-2648717991a8.png" width="2540" />
  </figure>
  <p id="57fo">Используя <a href="https://nuxt.com/docs/examples/composables/use-fetch" target="_blank">пример useFetch</a> из документации nuxt, добавляем javascript код отвечающий за асинхронное получения данных из API strapi front/components/CategorysSection.vue:</p>
  <pre id="wQjp">&lt;script setup&gt;
const config = useRuntimeConfig();
const { data: categories } = await useFetch(() =&gt; &#x60;/api/categories&#x60;, {
  baseURL: config.API_URL,
  headers: {&quot;Authorization&quot;: &quot;bearer &quot; + config.API_TOKEN},
  query: { &quot;populate&quot;: &quot;Image&quot;},
})
&lt;/script&gt;</pre>
  <p id="ty03">Так же используя пример <a href="https://vuejs.org/guide/essentials/list.html#v-for" target="_blank">отрисовки списков</a> и документации vue добавляем динамический рендеринг с помощью директивой <em>v-for </em>из полученных данных карточки каталогов.</p>
  <figure id="JtJz" class="m_column">
    <img src="https://img1.teletype.in/files/4e/3e/4e3ec8b7-953b-4c50-9751-804497510689.png" width="2206" />
  </figure>
  <p id="CvIC">Аналогичным образом настроим динамический рендеринг компоненты PopulerProduct.vue с популярными продуктами.<br />Далее настраиваем передачу параметров через конфиг <em>front/nuxt.config.ts</em>:</p>
  <pre id="wD2B">export default defineNuxtConfig({
    runtimeConfig: {
        API_TOKEN: &quot;&quot;,  // NUXT_API_TOKEN
        public: {
            API_URL: &quot;&quot;, // NUXT_PUBLIC_API_URL
        },
    },
    css: [
        &#x27;@/assets/scss/main.scss&#x27;
    ],
})</pre>
  <p id="FU5Q">И настраиваем эти параметры в IDE конфигурации запуска фронта</p>
  <figure id="cgDZ" class="m_column">
    <img src="https://img4.teletype.in/files/7f/cd/7fcdae1f-8d5a-450e-87b2-021c5fb3b4d7.png" width="1676" />
  </figure>
  <p id="OTKL">Запустим фронт и проверяем рендеринг главной странички</p>
  <figure id="yCR8" class="m_column">
    <img src="https://img2.teletype.in/files/d8/af/d8af8ac4-f946-420a-8c2b-97a02731b5bd.png" width="1464" />
  </figure>
  <p id="fjsA">Теперь добавим динамическую страничку с детализацией продукта <em>front/pages/product/[...slug].vue</em>:</p>
  <pre id="mHZq">&lt;template&gt;
  &lt;BreadCrumb /&gt;
  &lt;ProductDetails /&gt;
  &lt;FeaturesSection /&gt;
&lt;/template&gt;</pre>
  <p id="K42e">И в компоненту ProductDetails добавим асинхронное получение данных о продукте по его slug из параметров маршрутизации запросов <em>front/components/ProductDetails.vue</em>:</p>
  <pre id="gGUk">&lt;script setup&gt;
const slug = useRoute().params.slug
const config = useRuntimeConfig()
const { data: products } = await useFetch(() =&gt; &#x60;/api/products&#x60;, {
  baseURL: config.API_URL,
  headers: { &quot;Authorization&quot;: &quot;bearer &quot; + config.API_TOKEN },
  query: { &quot;filters\[Slug\][$eq]&quot;: slug, &quot;populate&quot;: &quot;Image&quot; }
})
&lt;/script&gt;</pre>
  <p id="tw5g">И аналогично помощью директивой <em>v-for </em>отрисуем список и передадим парамеры продукта в шаблонизатор</p>
  <figure id="HPkL" class="m_column">
    <img src="https://img1.teletype.in/files/c6/bd/c6bd32c9-fb9d-42e9-8821-0538fa450d5c.png" width="1446" />
  </figure>
  <p id="Q4sU">Так как в компоненте FeaturesSection используется автозапуск карусели на клиенте, то для корректного отображения установим и настроим карусель в nuxt</p>
  <pre id="Yf2h">yarn install vue3-carousel</pre>
  <p id="3fkq">Подключим Carousel в компоненте front/components/FeaturesSection.vue </p>
  <pre id="d2Qt">&lt;script&gt;
import { Carousel, Navigation, Slide } from &#x27;vue3-carousel&#x27;
import &#x27;vue3-carousel/dist/carousel.css&#x27;

export default defineComponent({
  name: &#x27;WrapAround&#x27;,
  components: {
    Carousel,
    Slide,
    Navigation,
  },
})
&lt;/script&gt;</pre>
  <p id="3igJ">И используем их в шаблоне </p>
  <figure id="bir2" class="m_column">
    <img src="https://img4.teletype.in/files/74/75/747569b8-9586-43d4-b5ac-7e986a05c2fc.png" width="1634" />
  </figure>
  <p id="HtH6">Проверяем рендеринг странички с продуктом</p>
  <figure id="2iWt" class="m_column">
    <img src="https://img3.teletype.in/files/ef/f5/eff598ff-b438-4052-923c-156e012bd1d4.png" width="2304" />
  </figure>
  <h3 id="U8Ut">Создаем корзину покупок</h3>
  <p id="nyvd">При нажатии на кнопку добавить в корзину будем добавлять ID - идентификатор продукта в список хранимый в локальном хранилище и на сервера.</p>
  <p id="kXJP">Использует <a href="https://pinia.vuejs.org/ssr/nuxt.html" target="_blank">документацию</a> по библиотеке Pinia настроим хранилище для списка покупок <em>front/store/cart.ts</em>:</p>
  <pre id="gDHP">import { defineStore, skipHydrate } from &#x27;pinia&#x27;
import { useLocalStorage } from &#x27;@vueuse/core&#x27;
export const useCartStore = defineStore(&#x27;cartStore&#x27;, () =&gt; {
    const cartItems = useLocalStorage(&#x27;pinia/cart&#x27;, [])
    function addValueToCartList(id: never) {
        cartItems.value.push(id)
    }
    return { addValueToCartList, cartItems: skipHydrate(cartItems)}
})</pre>
  <p id="Z8zo">Далее в компоненту PopulerProduct импортируем функцию addValueToCartList - добавления товаров в корзину покупок в компоненту <em>front/components/PopulerProduct.vue</em>:</p>
  <pre id="D72a">&lt;script setup&gt;
import { useCartStore } from &#x27;~/store/cart&#x27;
const cartStore = useCartStore()
const { addValueToCartList } = cartStore
...</pre>
  <p id="940K">И в шаблоне вызовем ее при клике на иконку с корзиной с помощь директивы <em>@click.prevent=&quot;addValueToCartList(item.id)&quot;</em></p>
  <figure id="EChh" class="m_column">
    <img src="https://img4.teletype.in/files/bb/c9/bbc95041-357d-41be-b7e6-6627c3424730.png" width="1598" />
  </figure>
  <p id="xlRS">Аналогично подключим функцию addValueToCartList в компонент с детализацией продукта <em>front/components/ProductDetails.vue:</em></p>
  <pre id="NqRE">&lt;a class=&quot;btn cart-bg &quot; href=&quot;#&quot; @click.prevent=&quot;addValueToCartList(item.id)&quot;&gt;Add to cart</pre>
  <p id="oOLL">Так же импортируем переменную cartItems для отображения числа товаров в компоненте <em>front/components/HeaderArea.vue:</em></p>
  <pre id="BDce">&lt;script setup&gt;
import { storeToRefs } from &#x27;pinia&#x27;
import { useCartStore } from &#x27;~/store/cart.ts&#x27;
const cartStore = useCartStore()
const { cartItems } = storeToRefs(cartStore)
&lt;/script&gt;</pre>
  <p id="HQOW">И в шаблон подставим переменную с функцией длинны списка:</p>
  <pre id="AsHZ">&lt;span class=&quot;cart&quot;&gt;{{ cartItems.length }}&lt;/span&gt;</pre>
  <p id="5SqD">Проверяем работу кнопок, отображения счетчика товаров и убеждаемся, что после обновления странички счетчик товаров не изменился.</p>
  <figure id="0Yd4" class="m_column">
    <img src="https://img1.teletype.in/files/0e/52/0e52e2dc-67fb-47f6-bd02-c51abdfda513.png" width="2312" />
  </figure>
  <p id="znyi">Теперь добавляем на страничку с покупками, где этот список будет использоваться для асинхронного получения данных о товарах в корзине. front/pages/cart.vue:</p>
  <pre id="4YOk">&lt;template&gt;
  &lt;BreadCrumb /&gt;
  &lt;Cart /&gt;
&lt;/template&gt;</pre>
  <p id="nQl5">Создаем компонуемый файл с товарами в корзине front/composables/useProducts.ts:</p>
  <pre id="YlbS">import { stringify } from &#x27;qs&#x27;;
export default async function (items: number[]) {
    const config = useRuntimeConfig();
    const {data: products} = await useFetch(() =&gt; &#x60;/api/products?&#x60; + stringify({
        filters: { id: { $in: items.length &gt; 0 ? items : [-1]}},
        populate: &quot;Image&quot;,
    }, { encodeValuesOnly: true}), {
        baseURL: config.API_URL,
        headers: {&quot;Authorization&quot;: &quot;bearer &quot; + config.API_TOKEN},
    })
    return products.value
}</pre>
  <p id="elU1">Так как список покупок хранится в локальном хранилище браузера нам потребуется серверный API для сохранения и получения списка покупок на стороне сервера. Создадим сильно упрощенный вариант без привязки к уникальной сессии пользователя.<br />front/server/api/cart.get.ts:</p>
  <pre id="tfMS">export default defineEventHandler(async (event) =&gt; {
    const body = await readBody(event)
    await useStorage().setItem(&#x27;db:cart&#x27;, body)
    return true
})</pre>
  <p id="CcjH">front/server/api/cart.post.ts:</p>
  <pre id="nv2u">export default defineEventHandler(async (event) =&gt; {
    const body = await readBody(event)
    await useStorage().setItem(&#x27;db:cart&#x27;, body)
    return true
})</pre>
  <p id="Utcy">Соответственно добавляем эти функции, функции удаления и количества в хранилище для списка покупок <em>front/store/cart.ts</em>:</p>
  <pre id="GuG7">...
    function removeCartItem(id: never) {
        cartItems.value = cartItems.value.filter((number, i) =&gt; number != id)
        pushCart()
    }
    function quantity(id: never) {
        return cartItems.value.filter((number, i) =&gt; number == id).length
    }
    async function pushCart() {
        console.log(&quot;pull cart&quot;, cartItems.value)
        await useFetch(&#x27;/api/cart&#x27;, {
            method: &#x27;post&#x27;,
            body: { items: cartItems.value }
        })
    }
    async function fetchCart() {
        const { data: resData } = await useFetch(&#x27;/api/cart&#x27;)
        if (!resData.value) {
            return
        }
        cartItems.value.length = 0
        cartItems.value.push(...resData.value.items)
    }
    return { removeCartItem, addValueToCartList, pushCart, fetchCart, quantity, cartItems: skipHydrate(cartItems)}
})</pre>
  <p id="oj8v">Далее создаем компонент с корзиной покупок <em>front/components/Cart.vue</em>:</p>
  <pre id="snkv">&lt;script setup&gt;
import { storeToRefs } from &#x27;pinia&#x27;
import { useCartStore } from &#x27;~/store/cart.ts&#x27;
const cartStore = useCartStore()
const { cartItems } = storeToRefs(cartStore)
const { fetchCart } = cartStore
const { quantity, removeCartItem } = cartStore
const config = useRuntimeConfig()
if (process.server) { await fetchCart()}
const products = await useProducts(cartItems.value.concat())
function removeProductItem(idx) {
  const id = products.data[idx].id
  products.data.splice(idx, 1)
  removeCartItem(id)
}
&lt;/script&gt;</pre>
  <p id="qeyX">И полученные данные отрисовываем с помощью директивы v-for и при клике на Delete вызываем функцию removeProductItem, которая реактивно перерисовывает список товаров, удаляет все товары с данным id из хранилища покупок с сохранением его на сервере. </p>
  <figure id="B3KT" class="m_column">
    <img src="https://img3.teletype.in/files/ee/fe/eefe7eaa-033e-4b23-bc35-ce0e49ae2350.png" width="1874" />
  </figure>
  <p id="NFDb">Еще необходимо вызывать функцию pushCart отправки списка покупок на сервер перед переходом на страничку корзины из компоненты <em>front/components/HeaderArea.vue</em>:</p>
  <pre id="Kgsk">...
const { pushCart } = cartStore
&lt;/script&gt;</pre>
  <p id="MVin">и в шаблоне вызовем при клике</p>
  <pre id="7NJH">&lt;a href=&quot;/cart&quot; @click=&quot;pushCart&quot;&gt;</pre>
  <p id="VMqN"> Проверим рендеринг странички с корзиной и кнопки удаления</p>
  <figure id="m1CR" class="m_column">
    <img src="https://img2.teletype.in/files/9b/ef/9befc58e-59e1-4b51-be0c-f57a27cb029c.png" width="2046" />
  </figure>
  <p id="l3LN">Так как для демонстрации мы взяли случайны шаблон магазина, а в вашем случае он может сильно отличатся по UX, то доводить это шаблон до полной функциональности смысла не вижу.</p>
  <p id="0f8p">Все примеры кода можно найти в моем репозитории <a href="https://github.com/kmlebedev/e-commerce-store-with-nuxt3-and-strapi" target="_blank">https://github.com/kmlebedev/e-commerce-store-with-nuxt3-and-strapi</a></p>

]]></content:encoded></item></channel></rss>