Skip to content

Интеграция с Nuxt ​

Модуль responsive-media/nuxt превращает ваши брейкпоинты в типизированные composables для всего приложения. Вы описываете их один раз в nuxt.config.ts; useResponsive(), useBreakpoints() и useResponsiveValue() после этого импортируются автоматически и знают ключи ваших брейкпоинтов — без обобщённого параметра и без обёртки-composable, которую нужно поддерживать. Модуль заботится и о серверном рендеринге: гидратации без несовпадений и сервере, который может рендерить под устройство посетителя.

Установка ​

bash
npm install responsive-media

Подключите модуль. Без опций он использует брейкпоинты пакета по умолчанию — mobile / tablet / desktop:

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['responsive-media/nuxt'],
})

Nuxt ^3.9.0 || ^4.0.0. Модулю нужен @nuxt/kit, который Nuxt уже предоставляет.

Конфигурация ​

Модуль читает ключ responsive в nuxt.config.ts, а редактор подсказывает и проверяет его. Все опции необязательны, и каждое значение должно быть обычными данными — оно записывается в сгенерированный файл.

  • breakpoints — Record<string, MediaQueryConfig> · по умолчанию: ResponsiveConfig пакета (mobile, tablet, desktop). Тот же формат, что и везде — см. Основные концепции.
  • order — string[] · по умолчанию: порядок ключей в breakpoints. Порядок, который используют current, isAbove(), isBelow() и between().
  • debounce — number · по умолчанию: 0. Задержка в миллисекундах, прежде чем изменение дойдёт до слушателей subscribe() общего состояния. Реактивное состояние Vue следует за этими слушателями, поэтому тоже задерживается; 0 отключает задержку.
  • ssrState — Record<string, boolean> · по умолчанию: не задано. Состояние, с которым рендерится сервер, и запасной вариант для подсказок ниже. Без него все ключи на сервере равны false.
  • hydration — 'immediate' | 'deferred' · по умолчанию: 'deferred'. При 'deferred' клиент гидратируется с теми же значениями, что использовал сервер, и переключается на настоящие, когда страница догидратировалась, поэтому несовпадения при гидратации нет. См. SSR и гидратация.
  • ssrHints — ('cookie' | 'user-agent')[] · по умолчанию: []. Как сервер угадывает раскладку посетителя по запросу, в этом порядке. Без подсказок сервер всегда рендерит ssrState.
  • ssrDevices — Partial<Record<'mobile' | 'tablet' | 'desktop', { width: number; height: number }>> · по умолчанию: 390x844, 820x1180, 1440x900. Размеры, для которых рендерится подсказка user-agent.
  • cookie — string · по умолчанию: 'responsive-viewport'. Имя cookie, которую использует подсказка cookie.
  • devBadge — boolean · по умолчанию: false. Небольшая плашка в углу страницы, только при разработке, с текущим брейкпоинтом и шириной окна.
  • css — { scss?: boolean; customMedia?: boolean } · по умолчанию: не задано. Генерирует модуль стилей из breakpoints — см. Интеграция с CSS.

Пример:

ts
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['responsive-media/nuxt'],

  responsive: {
    breakpoints: {
      mobile: [{ type: 'max-width', value: 600 }],
      smallTablet: [{ type: 'max-width', value: 850 }],
      tablet: [{ type: 'max-width', value: 960 }],
      desktop: [{ type: 'min-width', value: 961 }],
    },
    order: ['mobile', 'smallTablet', 'tablet', 'desktop'],
    ssrState: { desktop: true },
    ssrHints: ['cookie', 'user-agent'],
    css: { scss: true },
  },
})

Что регистрирует модуль ​

  • useResponsive(), useBreakpoints() и useResponsiveValue() — подключаются автоматически и типизированы по вашим breakpoints: useResponsive() возвращает { mobile: boolean; tablet: boolean; smallTablet: boolean; desktop: boolean }, useBreakpoints() принимает только эти ключи, и useResponsiveValue({ mobile: 1, tablet: 2 }) тоже принимает только их.
  • useMediaQuery(), useContainerState(), useUserPreferences() и useViewportSize() — подключаются автоматически из responsive-media/vue. useMediaQuery() принимает строку, ref или геттер, а useContainerState() выводит ключи из собственного конфига.
  • Плагин — устанавливает общее состояние на Vue-приложение, чтобы каждый компонент читал одно и то же реактивное состояние, а на сервере даёт каждому запросу своё.

Использование composables ​

vue
<script setup lang="ts">
const responsive = useResponsive()
const { current, isAbove } = useBreakpoints()
const columns = useResponsiveValue({ mobile: 1, tablet: 2, desktop: 4 })
const prefs = useUserPreferences()

const box = useTemplateRef<HTMLElement>('box')
const boxState = useContainerState(box, {
  narrow: [{ type: 'max-width', value: 300 }],
  roomy: [{ type: 'min-width', value: 301 }],
})
</script>

<template>
  <CompactLayout v-if="responsive.smallTablet" />
  <WideLayout v-else />

  <span>{{ current }}</span>
  <Grid :columns="columns" />
  <MobileOnly v-if="!isAbove('mobile')" />
  <div ref="box">{{ boxState.narrow ? 'narrow' : 'roomy' }}</div>
  <Moon v-if="prefs.dark" />
</template>

Типы ​

Состояние и ключи брейкпоинтов выводятся из breakpoints, поэтому опечатка — это ошибка компиляции, а не молчаливый undefined:

ts
const responsive = useResponsive()
responsive.smallTablet // boolean
responsive.nope // ошибка: свойства 'nope' не существует

const { isAbove } = useBreakpoints()
isAbove('smallTablet') // верно
isAbove('nope') // ошибка: нельзя присвоить 'mobile' | 'tablet' | 'smallTablet' | 'desktop'

useResponsiveValue({ nope: 1 }) // ошибка: 'nope' нет среди брейкпоинтов

Ключ responsive в nuxt.config.ts тоже типизирован: неизвестную опцию или значение вроде hydration: 'sometimes' редактор отметит как ошибку.

SSR и гидратация ​

Сервер не знает размера браузера, и модуль предлагает три уровня работы с этим — полная картина в SSR и гидратация:

  • Фиксированная догадка. ssrState — то, что рендерит сервер. Выбирайте раскладку, которую получает большинство посетителей, обычно desktop.
  • Без несовпадений. При hydration: 'deferred' по умолчанию браузер гидратируется со значениями сервера и переключается на свои настоящие, когда страница догидратировалась (когда Nuxt сообщает app:suspense:resolve, так что асинхронные компоненты охвачены). Vue не пишет предупреждения о гидратации, а страница рендерится дважды.
  • Лучшая догадка. ssrHints: ['cookie', 'user-agent'] позволяет серверу рендерить под устройство самого посетителя:
    • cookie — браузер записывает размер окна в cookie responsive-viewport (<ширина>x<высота>) после монтирования и при изменении размера окна, а сервер читает её при следующем запросе. Вернувшийся посетитель получает точную раскладку уже в первом HTML.
    • user-agent — сервер относит устройство к классу mobile, tablet или desktop и рендерит для ssrDevices этого класса.

Значения, которые использовал сервер, попадают в payload страницы, поэтому клиент гидратируется ровно с ними.

Кэширование. При включённых подсказках HTML зависит от заголовков Cookie и User-Agent. Сделайте так, чтобы CDN или прокси, кэширующие страницы, учитывали их в Vary (Vary: Cookie, User-Agent), либо оставьте ssrHints пустым. У предрендеренных страниц запроса нет, и они всегда используют ssrState.

Приватность. Cookie хранит размер окна браузера; её читает только ваш сервер.

У useMediaQuery() и состояния контейнера серверного значения нет: они false или пусты, пока управление не возьмёт браузер. Части страницы, которые от них зависят, помещайте в <ClientOnly>.

Плашка для разработки ​

devBadge: true показывает при разработке небольшую плашку в углу страницы — текущий брейкпоинт и ширину окна, например smallTablet · 800px. В production-сборку она не попадает никогда.

Как это работает ​

  • Сгенерированные файлы. Модуль пишет .nuxt/responsive-media/index.ts — вызов defineResponsive() с вашими breakpoints и опциями, — и плагин, который его устанавливает. Именно этот файл даёт composables их типы.
  • Одно общее состояние и по одному на запрос. defineResponsive() применяет конфигурацию к общему состоянию пакета при загрузке плагина. В браузере плагин использует это состояние; на сервере он создаёт отдельное состояние для каждого запроса, поэтому параллельные запросы с разными подсказками никогда не видят чужих значений.
  • Транспиляция. Модуль добавляет responsive-media в build.transpile, чтобы серверная сборка могла загрузить ESM-файлы пакета.
  • Без модуля. Адаптер Vue работает в Nuxt и сам по себе — импортируйте useResponsive из responsive-media/vue и вызывайте defineResponsive() самостоятельно, — но тогда плагин и типы вы подключаете вручную.