跪拜 Guibai
← Back to the summary

Three Ways to Serve PC and Mobile from a Single URL

Abstract: Through a two-step strategy of "identify device type → render corresponding content per device," use a single URL to display a Web application on PC and an H5 application on mobile. The core methods are divided into three layers: UA string detection and redirect (simplest), Framework Context/Inject to pass device info (component-level conditional rendering), and Responsive Hooks/Media Queries (pure CSS-driven). In one sentence—first ask "who are you," then give "what you should see."


1. Why Single-URL Dual-End Adaptation Is Needed

The traditional approach is to deploy two separate domains for PC and mobile (e.g., www.example.com and m.example.com), which introduces the following problems:

Problem Explanation
SEO Fragmentation Each domain has its own authority, making it hard for search engines to consolidate.
Sharing Fragmentation A link shared by a PC user might redirect to the PC version on a phone, creating a poor experience.
High Maintenance Cost Two sets of code, two deployment processes, two monitoring systems.
User Confusion Different URLs for the same product lead to inconsistent brand perception.

Core Idea: One entry point → identify device → render the corresponding view.

User visits https://example.com
        │
        ▼
   ┌─────────┐
   │ Identify │ ← "Are you a PC or a phone?"
   │ Device   │
   └────┬────┘
        │
   ┌────┴────┐
   ▼         ▼
 PC End    Mobile End
 Web App   H5 App
(Desktop   (Mobile
 Layout)    Layout)

2. Solution 1: User-Agent Detection + Page Redirect (Simplest)

Principle

When a browser requests a page, the request header carries a User-Agent field containing information about the browser, operating system, and device type. The current device type can be determined by parsing the UA string.

// Step 1: See what the UA looks like
console.log(navigator.userAgent)
// Chrome PC:   "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."
// iPhone Safari: "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 ..."
// Android Chrome: "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 ... Mobile ..."

// Step 2: Write the detection function
function isMobile() {
  return /Mobi|Android/i.test(navigator.userAgent)
}

// Step 3: Redirect based on the result
if (isMobile()) {
  window.location.href = "/mobile.html"  // Phone → H5 page
} else {
  // PC → Continue loading the current Web page (or redirect to /pc.html)
}

Enhanced Full Version (Covering More Scenarios)

/**
 * Device Detection Utility
 * Supports detecting: Phone / Tablet / PC / WeChat In-App Browser
 */
const DeviceDetector = {
  ua: navigator.userAgent,

  isMobile() {
    return /Mobi|Android|iPhone|iPod|BlackBerry|IEMobile/i.test(this.ua)
  },

  isTablet() {
    // Tablet: Not a phone but contains Pad / Tablet keywords, or iPad
    return /iPad|Tablet|PlayBook|Silk/i.test(this.ua) &&
           !/Mobi|Android|iPhone/i.test(this.ua)
  },

  isPC() {
    return !this.isMobile() && !this.isTablet()
  },

  isWeChat() {
    return /MicroMessenger/i.test(this.ua)
  },

  isIOS() {
    return /iPhone|iPad|iPod/i.test(this.ua)
  },

  isAndroid() {
    return /Android/i.test(this.ua)
  },

  /** Get device type */
  getType() {
    if (this.isWeChat()) return 'wechat'
    if (this.isMobile()) return 'mobile'
    if (this.isTablet()) return 'tablet'
    return 'pc'
  }
}

// Usage example
const deviceType = DeviceDetector.getType()
console.log(`Current device: ${deviceType}`)

if (deviceType === 'mobile') {
  window.location.href = '/h5/index.html'
}

Pros and Cons of UA Detection

Pros Cons
Simple implementation, just a few lines of code UA can be forged (DevTools can simulate)
Excellent compatibility, supported by all browsers New devices/browsers might be missed
Can be done on both server and client side Cannot detect window resize (shrinking a PC window won't switch to mobile)
Can cooperate with server-side SSR to directly output the correct page High regex maintenance cost, requires continuous updates

3. Solution 2: Framework Context / Inject to Pass Device Info

When using frameworks like React or Vue, there's no need for page redirects. Instead, identify the device type once at the application's top level, then pass it to all child components via the framework's state management mechanism. Child components conditionally render different UIs based on the device type.

React Implementation: Context + Provider

import React, { createContext, useContext } from 'react'

// 1. Create device context
const DeviceContext = createContext({
  isMobile: false,
  deviceType: 'pc'
})

// 2. Provide device info at the application's top level
function DeviceProvider({ children }) {
  const [deviceType, setDeviceType] = React.useState(() => {
    return /Mobi|Android/i.test(navigator.userAgent) ? 'mobile' : 'pc'
  })

  return (
    <DeviceContext.Provider value={{
      isMobile: deviceType === 'mobile',
      deviceType
    }}>
      {children}
    </DeviceContext.Provider>
  )
}

// 3. Use in child components
function Header() {
  const { isMobile, deviceType } = useContext(DeviceContext)

  return (
    <header className={deviceType}>
      {isMobile ? (
        <nav>📱 Mobile Navigation (Hamburger Menu)</nav>
      ) : (
        <nav>🖥️ PC Navigation (Full Menu Bar)</nav>
      )}
    </header>
  )
}

// 4. Mount in entry file
function App() {
  return (
    <DeviceProvider>
      <Header />
      <MainContent />
    </DeviceProvider>
  )
}

Vue 3 Implementation: provide / inject

<!-- App.vue — Top-level provide -->
<script setup>
import { provide, ref } from 'vue'

function isMobile() {
  return /Mobi|Android/i.test(navigator.userAgent)
}

// Very important API: provide injects device type into all child components
provide('deviceType', isMobile() ? 'mobile' : 'pc')
provide('isMobile', isMobile())
</script>

<template>
  <Header />
  <MainContent />
</template>

<!-- Header.vue — Child component inject -->
<script setup>
import { inject } from 'vue'

const deviceType = inject('deviceType')   // 'mobile' | 'pc'
const isMobile = inject('isMobile')       // true | false
</script>

<template>
  <header :class="deviceType">
    <!-- Show hamburger menu on mobile -->
    <button v-if="isMobile" class="hamburger">☰</button>

    <!-- Show full navigation on PC -->
    <nav v-else class="full-nav">
      <a href="/">Home</a>
      <a href="/products">Products</a>
      <a href="/about">About</a>
    </nav>
  </header>
</template>

Solution 1 vs Solution 2 Comparison

Dimension Solution 1 (UA Redirect) Solution 2 (Context/Inject)
Implementation Detect then window.location.href redirect Detect then store in state, conditional rendering in components
User Experience Page refresh/redirect feel No redirect, same-page switching (SPA experience)
Code Reuse PC and H5 might be two independent codebases One codebase, conditionally renders different UIs
Use Case Two completely independent pages / SSR projects SPA single-page applications / component-level differentiation
SEO Friendly Different URLs after redirect, needs extra handling Same URL, more SEO friendly
Complexity ⭐ Low ⭐⭐ Medium

4. Solution 3: Responsive Hooks + CSS Media Queries (Recommended)

The first two solutions are based on User-Agent (device type). The third solution is based on screen width (viewport size), which aligns more with responsive design principles. Even if a device is identified as a PC, if the window is shrunk very small, it should still display the mobile layout.

React: react-responsive Library

import React from 'react'
import { useMediaQuery } from 'react-responsive'

function App() {
  // Judge based on screen width, not UA
  const isDesktop = useMediaQuery({ minWidth: 1024 })
  const isMobile = useMediaQuery({ maxWidth: 1023 })
  const isTablet = useMediaQuery({ minWidth: 768, maxWidth: 1023 })

  return (
    <div>
      {/* PC End: Wide-screen layout */}
      {isDesktop && (
        <div className="desktop-layout">
          <h1>Desktop Web Application</h1>
          <p>This content is displayed on desktop devices.</p>
          <aside className="sidebar">Sidebar Navigation</aside>
          <main className="content">Main Content Area (Three-Column Grid)</main>
        </div>
      )}

      {/* Mobile End: Narrow-screen layout */}
      {isMobile && (
        <div className="mobile-layout">
          <h1>Mobile H5 Application</h1>
          <p>This content is displayed on mobile devices.</p>
          <nav className="bottom-tab">Bottom Tab Navigation</nav>
          <main className="content">Single Column Content Area</main>
        </div>
      )}

      {/* Tablet End: Medium layout */}
      {isTablet && (
        <div className="tablet-layout">
          <h1>Tablet Layout</h1>
          <p>Two-column layout for tablets.</p>
        </div>
      )}
    </div>
  )
}

Vue 3: Composable Encapsulation

<!-- composables/useResponsive.js -->
import { ref, onMounted, onUnmounted } from 'vue'

export function useResponsive(breakpoints = { mobile: 768, tablet: 1024 }) {
  const width = ref(window.innerWidth)

  const update = () => { width.value = window.innerWidth }

  onMounted(() => window.addEventListener('resize', update))
  onUnmounted(() => window.removeEventListener('resize', update))

  const isMobile = () => width.value <= breakpoints.mobile
  const isTablet = () => width.value > breakpoints.mobile && width.value <= breakpoints.tablet
  const isDesktop = () => width.value > breakpoints.tablet

  return { width, isMobile, isTablet, isDesktop }
}
<!-- App.vue -->
<script setup>
import { useResponsive } from './composables/useResponsive'

const { isMobile, isDesktop } = useResponsive()
</script>

<template>
  <div class="app">
    <DesktopLayout v-if="isDesktop()" />
    <MobileLayout v-else />
  </div>
</template>

Pure CSS Media Queries (Zero JS)

/* Default styles: Mobile First */
.container {
  display: flex;
  flex-direction: column;   /* Default single column for mobile */
  padding: 16px;
}

/* Tablet and above */
@media (min-width: 768px) {
  .container {
    flex-direction: row;
    gap: 24px;
  }

  .sidebar {
    width: 200px;
  }
}

/* PC End */
@media (min-width: 1024px) {
  .container {
    max-width: 1200px;
    margin: 0 auto;
  }

  .sidebar {
    width: 250px;
  }

  .content {
    display: grid;
    grid-template-columns: repeat(3, 1fr); /* Three-column grid */
  }
}

Comparison of the Three Solutions

Dimension UA Redirect Context/Inject Conditional Rendering Responsive Hooks / Media Queries
Judgment Basis User-Agent string User-Agent string Screen width (window.innerWidth)
Redirect? ✅ Yes ❌ No ❌ No
Responds to Resize? ❌ No ❌ No ✅ Real-time response
SPA Friendly Average (breaks single-page experience) ✅ Best ✅ Best
SSR Friendly ✅ Can judge on server ✅ Can initialize on server ⚠️ Requires client-side hydration
Implementation Complexity ⭐ Low ⭐⭐ Medium ⭐⭐~⭐⭐⭐
Recommended Scenario Two independent pages / Traditional MPA SPA component-level differences Responsive layout / Progressive enhancement

5. Server-Side Solution: Nginx / Backend Route Distribution

In addition to front-end detection, device identification and distribution can be completed by the server before the request reaches the front end:

Nginx Reverse Proxy Based on UA

# Proxy mobile requests to the H5 service based on User-Agent
server {
    listen 80;
    server_name example.com;

    # Mobile UA → Forward to H5 application
    if ($http_user_agent ~* "(iPhone|iPod|Android|Mobile|BlackBerry)") {
        rewrite ^/(.*)$ http://h5.example.com/$1 redirect;
        # Or different directory on the same server:
        # rewrite ^/(.*)$ /h5/$1 last;
    }

    # PC End → Normal forward to Web application
    location / {
        proxy_pass http://web_app:3000;
    }

    location /h5/ {
        proxy_pass http://h5_app:3001;
    }
}

Node.js / Express Middleware

// Express middleware: Server-side device detection
function deviceDetect(req, res, next) {
  const ua = req.headers['user-agent'] || ''
  const isMobile = /Mobi|Android|iPhone/i.test(ua)

  // Method A: Set response header for frontend to read
  res.setHeader('X-Device-Type', isMobile ? 'mobile' : 'desktop')

  // Method B: Directly render different templates
  req.deviceType = isMobile ? 'mobile' : 'desktop'
  next()
}

app.use(deviceDetect)

app.get('/', (req, res) => {
  if (req.deviceType === 'mobile') {
    res.render('mobile/index')   // Render H5 template
  } else {
    res.render('desktop/index')  // Render PC template
  }
})

Frontend vs Backend Solution Comparison

Dimension Frontend Detection (JS) Server-Side Detection (Nginx/Node)
Execution Timing After page loads When request arrives
First Screen Speed Load then redirect/render (has delay) Directly returns correct content (faster)
SEO ⚠️ Crawlers might not execute JS ✅ Returns correct HTML
Cacheability Difficult (results vary per client) Can cooperate with Vary: User-Agent
Flexibility High (can combine with interaction state) Low (can only look at UA)

6. Complete Practical Integration: E-commerce Site Single-URL Dual-End Adaptation

Below is a minimal runnable example that strings the above solutions together:

User visits https://shop.example.com
        │
        ▼
  ┌──────────────────┐
  │  ① Nginx UA Detect │  ← First line of defense: Server-side fast triage
  └────┬─────────┬───┘
       │         │
   Mobile UA    Desktop UA
       │         │
       ▼         ▼
  ┌────────┐ ┌────────┐
  │H5 SPA  │ │Web SPA │  ← Second line of defense: Framework conditional rendering
  └────┬───┘ └────┬───┘
       │          │
       ▼          ▼
  ┌────────┐ ┌────────┐
  │React   │ │React   │  ← Third line of defense: useMediaQuery fine-tuning
  │Mobile  │ │Desktop │
  │Layout  │ │Layout  │
  └────────┘ └────────┘

Code Implementation (React Version)

// index.js — Entry
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { DeviceProvider } from './context/DeviceContext'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <DeviceProvider>
      <App />
    </DeviceProvider>
  </React.StrictMode>
)

// context/DeviceContext.js — Device Context
import { createContext, useContext, useState } from 'react'

const DeviceContext = createContext()

export function DeviceProvider({ children }) {
  const [device, setDevice] = useState(() => ({
    type: /Mobi|Android/i.test(navigator.userAgent) ? 'mobile' : 'desktop',
    width: window.innerWidth
  })

  // Listen for window changes, allow dynamic adjustment on resize
  useState(() => {
    const handleResize = () => {
      setDevice(prev => ({
        ...prev,
        width: window.innerWidth
      }))
    }
    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  })

  return (
    <DeviceContext.Provider value={device}>
      {children}
    </DeviceContext.Provider>
  )
}

export const useDevice = () => useContext(DeviceContext)

// App.js — Main Application
import { useDevice } from './context/DeviceContext'
import DesktopLayout from './layouts/DesktopLayout'
import MobileLayout from './layouts/MobileLayout'

export default function App() {
  const { type, width } = useDevice()

  // Combined judgment: UA type + screen width as fallback
  const isMobileView = type === 'mobile' || width < 1024

  return isMobileView ? <MobileLayout /> : <DesktopLayout />
}

// layouts/MobileLayout.jsx — H5 Layout
export default function MobileLayout() {
  return (
    <div className="mobile-app">
      <header className="mobile-header">
        <span className="logo">🛒 Shop</span>
        <button className="search">🔍</button>
      </header>

      <main className="mobile-main">
        {/* Single column product card stream */}
        {[1, 2, 3].map(i => (
          <div key={i} className="product-card">
            <img src={`/img/p${i}.jpg`} alt="Product" />
            <div className="info">
              <h3>Product Name {i}</h3>
              <p className="price">¥99.00</p>
            </div>
          </div>
        ))}
      </main>

      <nav className="mobile-tabs">
        <span className="active">Home</span>
        <span>Categories</span>
        <span>Cart</span>
        <span>Mine</span>
      </nav>
    </div>
  )
}

// layouts/DesktopLayout.jsx — PC Layout
export default function DesktopLayout() {
  return (
    <div className="desktop-app">
      <header className="desktop-header">
        <span className="logo">🛒 Shop</span>
        <nav>
          <a href="/">Home</a><a href="/category">Categories</a><a href="/cart">Cart</a>
        </nav>
        <input placeholder="Search products..." />
      </header>

      <div className="desktop-body">
        <aside className="sidebar">
          <h3>Categories</h3>
          <ul><li>Electronics</li><li>Clothing</li><li>Food</li></ul>
        </aside>

        <main className="desktop-main">
          {/* Three-column product grid */}
          <div className="product-grid">
            {[1, 2, 3, 4, 5, 6].map(i => (
              <div key={i} className="product-card">
                <img src={`/img/p${i}.jpg`} alt="Product" />
                <h3>Product Name {i}</h3>
                <p className="price">¥99.00</p>
              </div>
            ))}
          </div>
        </main>
      </div>
    </div>
  )
}

7. High-Frequency Interview Q&A

Q1: What's the difference between User-Agent detection and media query detection? Which should I choose?

UA detection judges the device type (Is this a phone or a computer?), while media queries judge the screen size (How wide is this window?).

  • Choose UA: When you need to differentiate touch vs. mouse operation, or need to call native APIs (camera/location).
  • Choose media queries: For pure layout differences, or when you want automatic switching as the window shrinks.
  • Best Practice: Combine both—UA decides which code bundle to load initially, media queries handle layout fine-tuning as a fallback.

Q2: Why is it recommended to use Context/Inject instead of calling isMobile() in every component?

  • Performance: The UA string is parsed only once, and the result is shared globally.
  • Maintainability: Device judgment logic is centralized; changing the rule only requires one edit.
  • Testability: You can easily mock the device type to test UIs for different ends.
  • Consistency: Avoids contradictory judgments caused by inconsistent regex across different components.

Q3: How to choose between server-side and front-end detection?

  • Pages with high SEO requirements (official sites, e-commerce detail pages) → Server-side detection, ensuring crawlers get the correct HTML.
  • Admin panels, systems requiring login → Front-end detection is sufficient, simple and flexible.
  • In production, it's recommended to do both: Nginx for coarse-grained triage + frontend for fine-grained rendering.

Q4: How to choose between useMediaQuery and CSS @media?

  • Pure layout differences (spacing, column count, font size) → CSS @media, zero JS overhead.
  • Need to render completely different component trees (PC shows a sidebar, mobile shows a bottom tab) → useMediaQuery.
  • The two don't conflict and can be used simultaneously.

Q5: How to handle the case where a "PC browser window is shrunk to mobile width"?

This is the biggest blind spot of pure UA detection. Solutions:

  1. Combined Strategy: UA sets the initial value + resize listener dynamically adjusts (as in Chapter 6's practical code).
  2. Pure media query approach: Completely abandon UA, only look at width.
  3. Prompt the user: When an anomaly is detected, prompt "Please use landscape mode for a better experience."

8. Memory Mnemonic

One link for dual ends to show, first ask the device, then render.
UA redirect is simplest, Context injection is more graceful,
Media queries look at width, three layers combined are strongest.
Server-side does the first pass, frontend renders the second,
Resize fallback is the third, each pass better than the last.

One-sentence summary: The essence of adapting a single URL for PC/H5 is "device identification + conditional rendering." The path from simple to recommended is: UA redirect → Framework Context passing → Responsive Hooks. For production environments, a three-layer combination of Nginx (server-side triage) + Context (component rendering) + Media Query (layout fine-tuning) is recommended.