Server-side rendering
Works everywhere there's a UA header
Node, Bun, Deno, Cloudflare Workers, Vercel Edge, Fastly Compute@Edge, AWS Lambda. The library is platform-neutral — same import, same call shape.
get-browser is built for SSR from the ground up. The library:
- Never touches
windowornavigatorat import time. Importingget-browserin a Node module or worker is safe. - Falls back gracefully when there's no UA available — every detector returns
false,detect()returns'unknown'. - Accepts an explicit
{ userAgent, vendor }on every function, so you can pin detection to the incoming request.
import { detect, isMobile } from 'get-browser';
const browser = detect({ userAgent: req.headers['user-agent'] ?? '' });
When you pass an explicit userAgent, the library ignores globalThis.navigator entirely — handy for unit tests that run inside happy-dom / jsdom but want a clean UA.
Framework recipes
Pick your stack:
- Next.js Route
- Next.js Server Component
- Next.js Edge / Workers
- Remix
- Astro
- Express
- Fastify
- Hono
- Deno
- Bun
app/api/browser/route.ts
import { detect, isMobile } from 'get-browser';
export async function GET(req: Request) {
const userAgent = req.headers.get('user-agent') ?? '';
return Response.json({
browser: detect({ userAgent }),
mobile: isMobile({ userAgent }),
});
}
app/page.tsx
import { headers } from 'next/headers';
import { detect, browsers } from 'get-browser';
export default async function Page() {
const ua = (await headers()).get('user-agent') ?? '';
const browser = detect({ userAgent: ua });
return (
<main data-browser={browser}>
{browser === browsers.IE ? <UpgradeNotice /> : <App />}
</main>
);
}
app/api/browser/route.ts
export const runtime = 'edge';
import { detect } from 'get-browser';
export function GET(req: Request) {
return Response.json({
browser: detect({ userAgent: req.headers.get('user-agent') ?? '' }),
});
}
app/routes/index.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { detect } from 'get-browser';
export async function loader({ request }: LoaderFunctionArgs) {
return json({
browser: detect({ userAgent: request.headers.get('user-agent') ?? '' }),
});
}
src/pages/index.astro
---
import { detect } from 'get-browser';
const ua = Astro.request.headers.get('user-agent') ?? '';
const browser = detect({ userAgent: ua });
---
<html data-browser={browser}>
<body>
<slot />
</body>
</html>
import express from 'express';
import { detect, isMobile } from 'get-browser';
const app = express();
app.use((req, res, next) => {
const ua = req.get('user-agent') ?? '';
res.locals.browser = detect({ userAgent: ua });
res.locals.isMobile = isMobile({ userAgent: ua });
next();
});
import Fastify from 'fastify';
import { detect } from 'get-browser';
const app = Fastify();
app.addHook('preHandler', async (req, reply) => {
reply.locals = {
...reply.locals,
browser: detect({ userAgent: req.headers['user-agent'] ?? '' }),
};
});
import { Hono } from 'hono';
import { detect } from 'get-browser';
const app = new Hono();
app.get('/', (c) => {
const browser = detect({ userAgent: c.req.header('user-agent') ?? '' });
return c.json({ browser });
});
import { detect } from 'npm:get-browser';
Deno.serve((req) => {
return Response.json({
browser: detect({ userAgent: req.headers.get('user-agent') ?? '' }),
});
});
import { detect } from 'get-browser';
Bun.serve({
fetch(req) {
return Response.json({
browser: detect({ userAgent: req.headers.get('user-agent') ?? '' }),
});
},
});
Hydration mismatches
If you render different markup on the server (based on the incoming UA) than on the client (based on navigator), React will warn about hydration mismatches. Two safe options:
- Pass the server-resolved browser down as a prop / context. Don't call
detect()again on the client for the initial render — use the server's answer. - Wrap browser-specific UI in a "client only" boundary (e.g.
useEffector Next.js dynamic import with{ ssr: false }).
import { type Browser } from 'get-browser';
const ServerBrowserContext = React.createContext<Browser>('unknown');
function App({ initialBrowser }: { initialBrowser: Browser }) {
return (
<ServerBrowserContext.Provider value={initialBrowser}>
<Page />
</ServerBrowserContext.Provider>
);
}
Pattern: detect once, propagate
On the server, call detect({ userAgent }) once per request and stash it on the context. On the client, the same value flows down via props — zero re-detection, zero mismatch.
See also
- Framework integrations → — React hooks, Vue composables, Svelte stores, Solid signals
- Recipes — copy-paste patterns for common tasks
detect()API — full signature and behaviour