Настройка Shopify Hydrogen (React Storefront)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка Shopify Hydrogen (React Storefront)
Сложная
~2-4 недели
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка Shopify Hydrogen (React Storefront)

Hydrogen — официальный React-фреймворк от Shopify для headless commerce. Построен на Remix, оптимизирован для работы со Storefront API, деплоится на Oxygen (CDN-хостинг Shopify) или любой совместимый с Node.js хостинг.

Почему Hydrogen, а не просто Next.js

Next.js + Storefront API — рабочий вариант. Hydrogen добавляет:

  • Oxygen-деплой — бесплатный глобальный edge-хостинг, встроенный в Shopify (от плана Basic). Нет отдельной инфраструктуры для деплоя.
  • Remix routing — вложенные роуты, server-side data loading через loader, optimistic UI через action
  • Shopify-специфичные примитивыShopifyProvider, хуки для корзины, аналитики, кеш-стратегии
  • Cache API — управление кешированием на уровне Cloudflare Workers
  • Streaming SSR — Progressive HTML rendering из коробки

Инициализация проекта

npm create @shopify/hydrogen@latest
# Выброр: Demo Store / Hello World
# Деплой: Oxygen / Self-hosted

cd my-hydrogen-app
npm install
npm run dev

Переменные окружения (.env):

SHOPIFY_STORE_DOMAIN=my-store.myshopify.com
SHOPIFY_STOREFRONT_ACCESS_TOKEN=abc123...
SHOPIFY_PUBLIC_STORE_DOMAIN=my-store.myshopify.com
SESSION_SECRET=random-secret-string

Структура проекта

hydrogen-app/
├── app/
│   ├── components/      # UI компоненты
│   ├── lib/             # утилиты, Shopify-клиент
│   ├── routes/          # файловый роутинг Remix
│   │   ├── _index.tsx   # главная страница
│   │   ├── products.$handle.tsx   # страница продукта
│   │   ├── collections.$handle.tsx
│   │   └── cart.tsx
│   ├── styles/          # глобальные CSS
│   └── root.tsx         # корневой layout
├── public/
├── server.ts            # Oxygen/Node entry point
└── vite.config.ts

Роут продукта с loader

// app/routes/products.$handle.tsx
import { json, type LoaderFunctionArgs } from '@shopify/remix-oxygen';
import { useLoaderData, type MetaFunction } from '@remix-run/react';
import { getSelectedProductOptions, Analytics } from '@shopify/hydrogen';
import { AddToCartButton } from '~/components/AddToCartButton';

const PRODUCT_QUERY = `#graphql
  query Product($handle: String!, $country: CountryCode, $language: LanguageCode)
    @inContext(country: $country, language: $language) {
    product(handle: $handle) {
      id
      title
      handle
      descriptionHtml
      options {
        name
        values
      }
      selectedVariant: variantBySelectedOptions(
        selectedOptions: $selectedOptions
        ignoreUnknownOptions: true
        caseInsensitiveMatch: true
      ) {
        id
        availableForSale
        price {
          amount
          currencyCode
        }
        compareAtPrice {
          amount
          currencyCode
        }
        image {
          url
          altText
          width
          height
        }
      }
      variants(first: 250) {
        nodes {
          id
          availableForSale
          selectedOptions { name value }
          price { amount currencyCode }
        }
      }
    }
  }
` as const;

export async function loader({ params, request, context }: LoaderFunctionArgs) {
  const { handle } = params;
  const { storefront } = context;

  const selectedOptions = getSelectedProductOptions(request);

  const { product } = await storefront.query(PRODUCT_QUERY, {
    variables: {
      handle,
      selectedOptions,
      country: storefront.i18n.country,
      language: storefront.i18n.language,
    },
    cache: storefront.CacheShort(), // кеш на 1 минуту
  });

  if (!product) throw new Response('Not Found', { status: 404 });

  return json({ product });
}

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  return [
    { title: data?.product.title },
    { name: 'description', content: data?.product.descriptionHtml.slice(0, 160) },
  ];
};

export default function ProductPage() {
  const { product } = useLoaderData<typeof loader>();
  const { selectedVariant } = product;

  return (
    <div className="product">
      <h1>{product.title}</h1>
      <ProductPrice
        price={selectedVariant?.price}
        compareAtPrice={selectedVariant?.compareAtPrice}
      />
      <AddToCartButton
        disabled={!selectedVariant?.availableForSale}
        variantId={selectedVariant?.id}
      >
        {selectedVariant?.availableForSale ? 'В корзину' : 'Нет в наличии'}
      </AddToCartButton>

      {/* Аналитика — автоматически отправляет product viewed event */}
      <Analytics.ProductView
        data={{
          products: [{
            id: product.id,
            title: product.title,
            price: selectedVariant?.price.amount ?? '0',
            vendor: '',
            variantId: selectedVariant?.id ?? '',
            variantTitle: selectedVariant?.selectedOptions.map(o => o.value).join(' / ') ?? '',
            quantity: 1,
          }],
        }}
      />
    </div>
  );
}

Стратегии кеширования

Hydrogen предоставляет встроенные стратегии кеша на уровне Cloudflare Workers:

// server.ts — настройка стратегий
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    const { storefront } = createStorefrontClient({
      cache: await caches.open('hydrogen'),
      waitUntil: (p) => ctx.waitUntil(p),
      // ...
    });
  }
};

// В loader-ах:
storefront.CacheShort()      // 1 минута (volatile данные: корзина)
storefront.CacheLong()       // 1 час (продукты, коллекции)
storefront.CacheCustom({     // кастомный TTL
  mode: 'public',
  maxAge: 300,
  staleWhileRevalidate: 600,
})
storefront.CacheNone()       // без кеша (персонализированный контент)

Корзина через CartProvider

// app/components/AddToCartButton.tsx
import { CartForm } from '@shopify/hydrogen';

export function AddToCartButton({
  variantId,
  quantity = 1,
  disabled,
  children,
}: {
  variantId: string;
  quantity?: number;
  disabled?: boolean;
  children: React.ReactNode;
}) {
  return (
    <CartForm
      route="/cart"
      inputs={{ lines: [{ merchandiseId: variantId, quantity }] }}
      action={CartForm.ACTIONS.LinesAdd}
    >
      {(fetcher) => (
        <button
          type="submit"
          disabled={disabled || fetcher.state !== 'idle'}
        >
          {fetcher.state !== 'idle' ? 'Добавляем...' : children}
        </button>
      )}
    </CartForm>
  );
}

// app/routes/cart.tsx — обработчик действий корзины
import { cartAction } from '~/lib/cart';

export async function action({ request, context }: ActionFunctionArgs) {
  return cartAction({ request, context });
}

Деплой на Oxygen

# Линковка с Shopify store
npx shopify hydrogen link

# Деплой на Oxygen
npx shopify hydrogen deploy

# Просмотр логов
npx shopify hydrogen logs

Oxygen использует Cloudflare Workers — глобальная сеть, ближайший edge к пользователю. TTFB < 50 мс для большинства регионов.

Деплой на Vercel / собственный сервер

Если нужен собственный хостинг:

// vite.config.ts — переключение на Node.js adapter
import { defineConfig } from 'vite';
import hydrogen from '@shopify/hydrogen/vite';
import { nodePreset } from '@shopify/remix-oxygen';

export default defineConfig({
  plugins: [
    hydrogen(),
    nodePreset(), // вместо оксигенного
  ],
});

На Vercel — через @vercel/remix адаптер. На VPS — через node server.js с pm2.

Интеграция с CMS

Для управления контентом (баннеры, лендинги, блог) рядом с Hydrogen используется headless CMS:

  • Contentful — GraphQL API, встроенный Asset CDN
  • Sanity — GROQ-запросы, real-time обновления
  • Prismic — Slice Machine для компонентной структуры

Запросы к CMS делаются в loader параллельно с запросами к Shopify:

export async function loader({ context }: LoaderFunctionArgs) {
  const [{ product }, { hero }] = await Promise.all([
    context.storefront.query(PRODUCT_QUERY, { variables }),
    fetchFromCMS('home-hero'),
  ]);

  return json({ product, hero });
}

Сроки

MVP headless-магазина на Hydrogen с деплоем на Oxygen (каталог, продукт, корзина, чекаут): 4–6 недель. Полноценный проект с CMS, мультирынком, кастомной аналитикой, A/B-тестингом и CI/CD: 2–4 месяца.