You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13 App Router中媒体查询的无错实现方案

解决Next.js 13 App Router侧边栏响应式渲染的Hydration与window未定义问题

方案1:优先用CSS媒体查询(最推荐,无JS问题)

直接用CSS处理响应式,完全绕开window对象和Hydration问题,这是响应式设计的原生方案,性能更好也更简单。

示例代码:

'use client';

export default function Sidebar() {
  return (
    <div className="sidebar">
      {/* 侧边栏内容 */}
    </div>
  );
}

对应的CSS(可放在全局样式或组件模块样式中):

/* 大屏样式(≥768px):侧边栏展开 */
.sidebar {
  width: 250px;
  transition: width 0.3s ease;
  display: block;
}

/* 小屏样式(<768px):侧边栏收起或隐藏 */
@media (max-width: 767px) {
  .sidebar {
    width: 60px;
    /* 如果需要完全隐藏,换成 display: none; */
    /* 或者用 transform: translateX(-100%); 实现滑入滑出 */
  }
}

方案2:用useEffect延迟初始化并监听媒体查询(适合必须用JS控制的场景)

利用useEffect仅在客户端执行的特性,先给服务端一个默认初始状态,客户端挂载后再同步实际媒体查询结果,并监听变化。

示例代码:

'use client';

import { useState, useEffect } from 'react';

export default function Sidebar() {
  // 服务端渲染时用默认值(比如假设是大屏状态)
  const [isSidebarOpen, setIsSidebarOpen] = useState(true);

  useEffect(() => {
    // 客户端初始化时获取当前媒体查询状态
    const mediaQuery = window.matchMedia('(min-width: 768px)');
    
    // 更新为客户端实际的媒体查询结果
    setIsSidebarOpen(mediaQuery.matches);

    // 监听屏幕尺寸变化,实时更新状态
    const handleMediaChange = (e) => {
      setIsSidebarOpen(e.matches);
    };

    mediaQuery.addEventListener('change', handleMediaChange);

    // 组件卸载时清理监听
    return () => mediaQuery.removeEventListener('change', handleMediaChange);
  }, []);

  return (
    <div className={`sidebar ${isSidebarOpen ? 'open' : 'closed'}`}>
      {/* 侧边栏内容 */}
    </div>
  );
}

方案3:用useSyncExternalStore同步媒体查询状态(React官方推荐的外部状态管理)

如果需要更严谨的状态同步,避免短暂的UI闪烁,可以用React的useSyncExternalStore来订阅媒体查询,确保服务端和客户端初始状态一致。

示例代码:

'use client';

import { useSyncExternalStore } from 'react';

// 获取媒体查询匹配结果,服务端返回默认值
function getMediaQueryMatches(query) {
  if (typeof window === 'undefined') return true;
  return window.matchMedia(query).matches;
}

// 订阅媒体查询变化
function subscribeMediaQuery(query, callback) {
  const mediaQuery = window.matchMedia(query);
  mediaQuery.addEventListener('change', callback);
  return () => mediaQuery.removeEventListener('change', callback);
}

export default function Sidebar() {
  // 同步媒体查询状态,服务端和客户端初始值一致
  const isLargeScreen = useSyncExternalStore(
    (callback) => subscribeMediaQuery('(min-width: 768px)', callback),
    () => getMediaQueryMatches('(min-width: 768px)'),
    () => getMediaQueryMatches('(min-width: 768px)')
  );

  return (
    <div className={`sidebar ${isLargeScreen ? 'open' : 'closed'}`}>
      {/* 侧边栏内容 */}
    </div>
  );
}

关键说明

  • 方案1是最优解,因为CSS响应式是浏览器原生支持的,不需要JS介入,完全避免服务端渲染的冲突。
  • 方案2和3适合需要结合JS交互逻辑的场景(比如侧边栏手动切换+响应式自动切换),核心是确保服务端和客户端初始渲染的UI一致,再在客户端同步实际状态。

内容的提问来源于stack exchange,提问作者Jaanis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 17:15:55