프로젝트를 진행하면서 모바일과 데스크탑을 분리해야 할 필요성이 생겼습니다.
이번 글에서는 그 이유와 함께, 처음 시도했던 방식의 한계, 그리고 middleware를 활용한 해결 방안까지 정리해보려 합니다.
아래와 같이 캐릭터 정보와 길드 정보에 대한 부분들을 보시면 많은 유저분들이 Desktop에서 정보를 확인하는것이 흥미로운 서비스라고 판단을 했습니다.
또한 모바일에 대한 부분을 반응형으로만 진행을 하는것보다는 추후 사용자를 위해서 모바일 UI개발을 하는것이 좋겠다 라는 생각에 현재는 Desktop의 사용자만을 위한 서비스를 런칭해볼까 해서 이렇게 분리를 선택하였습니다.


모바일로 접근시 아래와 같은 UI를 보여줄 예정입니다.
처음 방식은 Global Layout에서 Mobile과 Desktop을 조건부로 구분하여, 화면을 랜더링 하는것으로 구현을 하였습니다.
// layout.tsx
const ua = userAgent({ headers: await headers() })
const viewport = ua.device.type === 'mobile' ? 'mobile' : 'desktop'
return (
<>{viewport === 'desktop' ? <DesktopLayout /> : <Mobile404 />}</>
)
이렇게 userAgent를 사용하여, 사용자의 기기를 탐지하고, 그것으로 분리하면 Desktop 환경과 Mobile환경을 분리할수있었습니다.
그 전에 userAgent를 간단하게 라도 알아보고 시작하죠.
userAgent는 사용자가 웹사이트에 접속할 때 브라우저가 서버로 보내는 문자열을 뜻합니다.
이 문자열을 통해 브라우저의 정보, 운영 체제, 기기 등을 알수있는것입니다.
그래서 userAgent는 웹 브라우저별로 맞춤화된 서비스를 제공하기 위해서 만들어졌습니다.
📗Next.js 공식 문서
import { NextRequest, NextResponse, userAgent } from 'next/server'
export function middleware(request: NextRequest) {
const url = request.nextUrl
const { device } = userAgent(request)
// device.type can be: 'mobile', 'tablet', 'console', 'smarttv',
// 'wearable', 'embedded', or undefined (for desktop browsers)
const viewport = device.type || 'desktop'
url.searchParams.set('viewport', viewport)
return NextResponse.rewrite(url)
}
Next.js 공식 문서에는 전체 구조가 나오진 않지만, device.type은 mobile, desktop, tablet 중 하나임을 명시되어 반환한다고 나오네요
즉, 저희는 이것을 통해서 사용자의 디바이스 타입을 분리할수있습니다.
userAgent를 통해 레이아웃에서 분기하여 화면만 조건부로 렌더링했지만, 모바일과 데스크탑의 모든 컴포넌트의 리소스가 포함되는 구조였습니다.
모바일 페이지는 단순한 구조임에도 개발자 도구를 통해 확인해보면 1.3MB의 리소스를 불러옵니다.


→ 즉, 데스크탑용 컴포넌트(GNB, Footer 등)까지 모두 로딩된 결과입니다.
데스크탑 역시 모바일 페이지 리소스까지 모두 포함된 상태입니다.
결론적으로 조건부 렌더링만으로는 저희가 원하는 최적화는 불가합니다.


지금은 리소스가 큰편은 아니지만 서비스의 확장성을 고려하고, 많은 유저가 생긴다고 가정하면 더 더욱 UX 개선은 필수입니다.
//src/middleware.ts
import { NextResponse, userAgent } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/') {
const { device } = userAgent(request)
const viewport = device.type === 'mobile' ? 'mobile' : 'desktop'
request.nextUrl.pathname = `viewport/${viewport}`
// 사용자는 여전히 '/' 경로에 있지만,
// 내부적으로는 /viewport/desktop' 혹은 '/viewport/mobile'을 보여줍니다.
return NextResponse.rewrite(request.nextUrl)
}
return NextResponse.next()
}
export const config = {
matcher: ['/'], // 또한 루트 경로에만 동작
}
폴더 구조를 아래와 같이 viewport/desktop과 viewport/mobile로 분리합니다.
src
└── app
├── desktop
│ └── page.tsx ✅ 데스크탑용
├── mobile
│ └── page.tsx ✅ 모바일용
✅ 성능 개선 효과
| 구현 방식 | 리소스 |
|---|---|
| 레이아웃 조건부 | 1.3MB |
| middleware | 736KB |


초기에는 userAgent를 활용해 레이아웃에서 조건부 렌더링을 시도했지만, 이 방식은 결국 리소스 최적화에 실패한다는 문제를 겪었습니다. 반면, middleware를 통해 라우팅 레벨에서 분리하자 리소스 사용량이 약 40퍼가 줄어들고, 사용자에게 필요한 화면만 효율적으로 제공할 수 있었습니다.
앞으로도 프로젝트 규모가 커지거나 모바일 UX 고도화가 필요한 경우, 이런 라우팅 기반 분기 전략은 좋은 선택이 될 수 있다고 생각합니다.