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

NextJS中分离移动端与桌面端代码及独立页面实现问询

NextJS 实现独立桌面/移动端页面方案

1. User Agent 检测与页面路由

核心思路

通过 NextJS 的 Middleware 全局拦截请求,检测请求头中的 User Agent,将请求重写(或重定向)到对应设备的专属页面目录。同时支持用户手动切换设备类型(通过 Cookie 覆盖 UA 检测结果),弥补 UA 识别的局限性。

实现步骤

第一步:编写 UA 检测工具函数

在 src/utils/device.ts 中封装检测逻辑:

export function isMobileUserAgent(userAgent: string): boolean {
  const mobilePattern = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
  return mobilePattern.test(userAgent);
}

第二步:配置 Middleware 路由规则

在项目根目录创建 middleware.ts(NextJS 13+ App Router 支持),实现全局路由分发:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { isMobileUserAgent } from './src/utils/device';

export function middleware(request: NextRequest) {
  const userAgent = request.headers.get('user-agent') || '';
  const deviceCookie = request.cookies.get('preferred-device');
  
  // 优先使用用户手动设置的设备偏好
  let isMobile: boolean;
  if (deviceCookie) {
    isMobile = deviceCookie.value === 'mobile';
  } else {
    isMobile = isMobileUserAgent(userAgent);
  }

  // 排除静态资源、API 和已在对应设备路径的请求
  const isExcluded = 
    request.nextUrl.pathname.startsWith('/_next') || 
    request.nextUrl.pathname.startsWith('/api') ||
    (isMobile && request.nextUrl.pathname.startsWith('/mobile')) ||
    (!isMobile && request.nextUrl.pathname.startsWith('/desktop'));

  if (isExcluded) {
    return NextResponse.next();
  }

  // 重写路径到对应设备目录
  const targetPath = isMobile 
    ? `/mobile${request.nextUrl.pathname === '/' ? '' : request.nextUrl.pathname}`
    : `/desktop${request.nextUrl.pathname === '/' ? '' : request.nextUrl.pathname}`;

  return NextResponse.rewrite(new URL(targetPath, request.url));
}

// 指定 Middleware 作用的路径(可选,默认所有路径)
export const config = {
  matcher: ['/', '/about/:path*', '/contact/:path*'], // 替换为你的业务路径
};

第三步:添加手动切换设备入口

在桌面/移动端页面中添加切换按钮(客户端组件),让用户可以手动切换设备版本:

'use client';

import { useRouter } from 'next/navigation';
import { setCookie } from 'cookies-next';

// 桌面端页面的切换按钮
export function SwitchToMobileButton() {
  const router = useRouter();

  const handleSwitch = () => {
    setCookie('preferred-device', 'mobile', { path: '/', maxAge: 60 * 60 * 24 * 7 });
    router.refresh();
  };

  return <button onClick={handleSwitch}>切换到移动端版本</button>;
}

// 移动端页面的切换按钮
export function SwitchToDesktopButton() {
  const router = useRouter();

  const handleSwitch = () => {
    setCookie('preferred-device', 'desktop', { path: '/', maxAge: 60 * 60 * 24 * 7 });
    router.refresh();
  };

  return <button onClick={handleSwitch}>切换到桌面端版本</button>;
}

2. 代码架构设计

不需要创建两个独立的 src 文件夹,这种方式会大幅增加维护成本(重复配置、共享代码跨目录引用复杂)。推荐采用「单 src 下分设备目录 + 共享资源抽离」的架构:

推荐目录结构(App Router)

src/
├── app/
│   ├── desktop/          # 桌面端专属页面
│   │   ├── layout.tsx    # 桌面端全局布局
│   │   ├── page.tsx      # 桌面端首页
│   │   ├── about/
│   │   │   └── page.tsx  # 桌面端关于页
│   ├── mobile/           # 移动端专属页面
│   │   ├── layout.tsx    # 移动端全局布局
│   │   ├── page.tsx      # 移动端首页
│   │   ├── about/
│   │   │   └── page.tsx  # 移动端关于页
├── components/
│   ├── shared/           # 跨设备共享组件(按钮、输入框等)
│   │   ├── Button.tsx
│   │   └── Input.tsx
│   ├── desktop/          # 桌面端专属组件(仅桌面端用)
│   │   └── DesktopHeader.tsx
│   ├── mobile/           # 移动端专属组件(仅移动端用)
│   │   └── MobileNavbar.tsx
├── utils/
│   ├── device.ts         # UA 检测工具
│   └── common.ts         # 通用工具函数(日期处理、请求封装等)
├── styles/
│   ├── shared.css        # 全局共享样式
│   ├── desktop.css       # 桌面端专属样式
│   └── mobile.css        # 移动端专属样式

架构优势

  1. 维护成本低:所有代码在同一仓库下,无需同步两个 src 文件夹的配置和依赖。
  2. 共享资源复用:通用组件、工具函数只需编写一次,两端直接引用。
  3. 职责清晰:设备专属页面/组件与共享资源分离,便于团队协作开发。
  4. 扩展性强:后续新增平板端等设备,只需新增对应目录并修改 Middleware 规则即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 17:17:24