Redirecting mobile users to App or Play Store in NextJS

You want one URL — printed on a sticker, stamped into a QR code, pasted in an email — that takes a visitor to your app in the store appropriate to their device. iOS users land in the App Store, Android users land in Play Store, everyone else gets a sensible fallback. You don't want to maintain three URLs, and you don't want to send your audience through a third-party redirect service that owns the link forever.

Next.js middleware handles this in about thirty lines.

The middleware

1// middleware.ts 2import type { NextRequest } from 'next/server' 3import { NextResponse, userAgent } from 'next/server' 4 5const APP_STORE = 'https://apps.apple.com/app/yourapp/id123456789' 6const PLAY_STORE = 'https://play.google.com/store/apps/details?id=com.yourapp' 7const FALLBACK_URL = 'https://yourdomain.com/about-the-app' 8 9export function middleware(req: NextRequest) { 10 const { device, os } = userAgent(req) 11 12 if (os.name === 'iOS' || device.vendor === 'Apple') { 13 return NextResponse.redirect(APP_STORE) 14 } 15 if (os.name === 'Android') { 16 return NextResponse.redirect(PLAY_STORE) 17 } 18 return NextResponse.redirect(FALLBACK_URL) 19} 20 21export const config = { 22 matcher: '/get', 23}

The userAgent helper in next/server parses the request for you and reads UA Client Hints (Sec-CH-UA-Platform, Sec-CH-UA-Mobile) when the browser sends them, falling back to the legacy User-Agent string otherwise. That's better than rolling your own regex — the regex approach still works, but it misses the modern signal Chromium-based browsers prefer to send.

A few details worth being precise about:

  • The Apple URL is apps.apple.com/app/... with no country code. If you write apps.apple.com/in/app/... you've pinned everyone to the India storefront. The country-less form lets Apple resolve the visitor's region.
  • The matcher means this middleware only runs on /get. No placeholder page is needed — the middleware intercepts before any route handler runs. If you do create app/get/page.tsx as a safety net, just redirect('/') and stop; don't bother returning JSX since redirect throws.
  • The Android Play Store link will open the Play Store app on most devices. Behaviour around the install prompt has shifted across Play Store versions; don't promise a specific UI in your copy.

What it doesn't handle

The redirect-to-store flow assumes the visitor doesn't have your app installed. For users who do, the right tool is Universal Links (iOS) and App Links (Android). If you publish the appropriate apple-app-site-association and Digital Asset Links files at /.well-known/, the OS intercepts a tap on https://yourdomain.com/get and opens the app directly. The /get middleware then only runs as the fallback when the app isn't installed.

For Safari users who land on your marketing site separately, the iOS Smart App Banner is a one-line meta tag:

1<meta name="apple-itunes-app" content="app-id=123456789"> 2</meta>

That gives iOS Safari a native install bar at the top of the page without any JavaScript.

Why host the redirect yourself

The handful of services that do this for you charge per click and own the domain in your printed materials. If you stop paying — or they shut down — your QR codes turn into dead links. Hosting the redirect on your own domain means SEO accrues to you, the link works as long as your domain works, and you can wire in your own analytics whenever you decide you want them.