Интеграция с Nuxt
Модуль responsive-media/nuxt превращает ваши брейкпоинты в типизированные composables для всего приложения. Вы описываете их один раз в nuxt.config.ts; useResponsive(), useBreakpoints() и useResponsiveValue() после этого импортируются автоматически и знают ключи ваших брейкпоинтов — без обобщённого параметра и без обёртки-composable, которую нужно поддерживать. Модуль заботится и о серверном рендеринге: гидратации без несовпадений и сервере, который может рендерить под устройство посетителя.
Установка
npm install responsive-mediaПодключите модуль. Без опций он использует брейкпоинты пакета по умолчанию — mobile / tablet / desktop:
// 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.
Пример:
// 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
<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:
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— браузер записывает размер окна в cookieresponsive-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()самостоятельно, — но тогда плагин и типы вы подключаете вручную.