前言
在网站开发中,我们通常需要PC端和移动端适配不同的页面,例如,Nuxt 官网,当宽度大于1024px时展示PC界面,小于时展示移动端。(注:如果你的网站页面只有一套代码,只是通过css样式适配手机端,下面方法对你帮助不大)

适配方案解析
方法一:根据设备类型动态切换组件
通过判断设备类型,动态渲染 PC 端或移动端组件。
实现代码
vue
<template>
<ClientOnly>
<component :is="isMobile ? Mobile : Desktop" />
</ClientOnly>
</template>
<script setup>
import Desktop from "~/components/Desktop.vue";
import Mobile from "~/components/Mobile.vue";
const isMobile = ref(false);
//屏幕尺寸改变
const handleResize = () => {
isMobile.value = getDeviceType() === 'mobile'
}
//监听屏幕宽度
onMounted(() => {
handleResize();
window.addEventListener("resize", handleResize)
})
//获取设备类型
const getDeviceType = () => {
let UA
if (process.client) {
// 如果是在客户端执行,则通过 navigator 获取 user-agent
UA = navigator.userAgent
} else {
// 如果是在服务端执行,则通过请求头获取 user-agent
UA = useRequestHeader('user-agent')
}
let type;
if (/(Android|webOS|iPhone|iPod|tablet|BlackBerry|Mobile)/i.test(UA)) {
type = 'mobile'
} else {
type = 'desktop'
}
return type
}
</script>
效果

从效果中可以看出,能够识别PC端移动端,但在移动端刷新时会闪屏PC端页面,并且项目generate之后,刷新这个页面还会出现水合问题"Hydration node mismatch"
这是因为,服务端渲染(SSR)与客户端渲染不一致造成的,如果我们启用了SSR,Nuxt会提前为我们预渲染生成静态代码,而预渲染时是不知道设备类型的,导致预渲染时使用默认端,代码中 isMobile 默认是 false,因此预渲染的静态代码中也就是电脑端代码,从浏览器源代码中可以看出。

解决方法: 将整个组件使用<ClientOnly>
包裹,只在客户端渲染。
xml
<template>
<ClientOnly> <component :is="isMobile ? Mobile : Desktop" /> </ClientOnly>
</template>
这样虽然能能够解决闪屏和水合问题,但这样出来的页面body里面没有内容,对于SEO非常不友好,不推荐使用。
方法二:通过路由配置动态切换页面
将 PC 端和移动端页面分别放在独立目录,通过路由配置动态匹配设备类型对应的页面路径。当我们访问/abc时,通过路由router options 动态修改路由,跳转到/mobile/abc或者/desktop/abc中。
实现结构
txt
pages/
├─ desktop/
│ └─ index.vue # PC 端首页
└─ mobile/
└─ index.vue # 移动端首页
路由配置(app/router.options.ts)
typescript
import type {RouterConfig} from '@nuxt/schema';
import {useDeviceType} from '@/composables/useDeviceType';
export default <RouterConfig> {
routes: (_routes) => {
const targetType = useDeviceType().value; // 获取设备类型(mobile/desktop)
const notTargetType = targetType === 'mobile' ? 'desktop' : 'mobile';
// 匹配当前设备的路由(移除前缀)
const targetRoutes = _routes
.filter(item => (item.name as string).startsWith(targetType))
.map(item => ({
...item,
path: item.path.replace(`/${targetType}`, '') || '/'
}));
// 非当前设备的路由(保留前缀)
const notTargetRoutes = _routes
.filter(item => (item.name as string).startsWith(notTargetType))
.map(item => ({
...item,
path: !item.path.startsWith(`/${notTargetType}`)
? `/${notTargetType}${item.path}`
: item.path
}));
return [...targetRoutes, ...notTargetRoutes];
}
};
效果

存在问题
- 预渲染冲突:nuxt 默认会在构建时访问这些路由得到渲染结果,而这种方法会动态修改路由,得到的渲染结果的文件夹页面路径会是混乱的,无法生成静态页面。
- 页面缺失风险:若 PC 端与移动端页面数量不一致(如 PC 有 /abc 页面而移动端无),移动端访问 /abc 会出现 404 错误。
- 维护成本高:需严格保持两端页面目录结构一致,否则易出现路由匹配异常。
只有在dev环境下可以切换,且每次切换需要刷新页面,不推荐使用。
方法三:基于媒体查询的样式控制(推荐)
将 PC 端和移动端组件同时引入页面,通过 CSS 媒体查询控制显示 / 隐藏,两端组件同时渲染在 DOM 中,仅通过样式控制显示,避免组件动态切换导致的内容重绘闪烁,实现无闪屏切换(Nuxt 官网同款方案)。
实现代码
xml
<template>
<div class="desktop-container">
<Desktop />
</div>
<div class="mobile-container">
<Mobile />
</div>
</template>
<script setup>
import Desktop from "~/components/Desktop.vue";
import Mobile from "~/components/Mobile.vue";
import { useHead } from "nuxt/app";
// 配置视口(确保适配准确性)
useHead({
meta: [
{
name: "viewport",
content: "width=device-width, initial-scale=1.0"
}
]
});
</script>
<style scoped>
/* 默认显示 PC 端,隐藏移动端 */
.desktop-container { display: block; }
.mobile-container { display: none; }
/* 屏幕宽度 ≤1024px 时切换为移动端 */
@media (max-width: 1024px) {
.desktop-container { display: none; }
.mobile-container { display: block; }
}
</style>
效果

这种方法使用 display 属性来控制不同屏幕尺寸下组件的显示与隐藏,能够依据屏幕尺寸的变化切换对应的组件。预渲染页面里也涵盖了完整的源代码,对 SEO 很友好。不过,源代码中会同时存在移动端和 PC 端的代码,但这对整体的影响并不大,所以还是推荐使用这种方案。
要留意 head 中是否存在 width=device-width, initial-scale=1
的设置,一般情况下是默认有的。要是没有的话,就需要添加以下这段代码:

js
useHead({ meta: [ { name: "viewport", content: "width=device-width, initial-scale=1" } ] })
- width:该参数用于设定视口的宽度,可以将其设置为具体的像素值,也可以设置为 device-width(也就是设备屏幕的宽度)。例如 width=device-width,它能够让页面宽度自动去适配设备的屏幕宽度。
- initial-scale:此参数用于设置页面首次加载时的缩放比例,1.0 表示不进行缩放。比如 initial-scale=1.0,它可以确保页面按照原始尺寸正常显示,不会有默认的缩放情况。
在 safari 浏览器中,viewport 的默认宽度为 980px,要是没有指定 viewport 的宽度,就会默认按照 980px 来处理。当监测到屏幕尺寸小于 980px 并切换页面时,可能会出现闪屏 PC 端页面的情况,这是因为浏览器会先清除原有的设置,使用默认的 viewport,然后再根据媒体查询进行渲染。
所以,这里推荐使用 980px 或者 1024px 作为临界值。1024px 是平板 viewport 的默认宽度,如果希望平板使用手机端的页面,那么就使用 1024px 作为临界值;如果希望平板使用 PC 端的页面,那么就使用 980px。Nuxt 采用的就是这种方式,并且将临界值设定为了 1024px。
方案对比总结
方案 | 核心原理 | 优点 | 缺点 | 适用场景 |
方法一 | 动态切换组件 | 实现简单,组件隔离清晰 | 存在闪屏和水合问题,影响 SEO | 非 SSR 项目,或对 SEO 要求极低的场景 |
方法二 | 动态修改路由路径 | 路由层级清晰,完全隔离两端页面 | 预渲染不支持,易出现 404,维护成本高 | 纯客户端渲染项目,且两端页面结构差异极大 |
方法三(推荐) | 媒体查询控制显示 / 隐藏 | 无闪屏,支持 SSR / 预渲染,SEO 友好 | DOM 中存在冗余节点(影响小) | 需独立组件适配,且重视体验和 SEO 的项目 |
因此,兼顾体验、兼容性和 SEO,使用媒体查询控制是 Nuxt 等 SSR 框架兼容PC端、移动端比较好的一种方式。
以上就是全部内容了,如果你还有比较好的方式或实现思路欢迎评论区讨论。