Next.js Discord

Discord Forum

Device detection on the server

Unanswered
Acacia-ants posted this in #help-forum
Open in Discord
Acacia-antsOP
Hello, to whoever might read this.

I need to detect the device type so that I can render different components based on it. The docs that I found https://nextjs.org/docs/app/api-reference/functions/userAgent suggest that I should do it inside a middleware and rewrite the url. However, I already have a middleware that I set up for nexti18n and I am having a hard time making both work together.

My current middleware:
import { i18nRouter } from 'next-i18n-router';
import { i18nConfig } from './i18nConfig';
import { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
return i18nRouter(request, i18nConfig);
}

export const config = {
matcher: '/((?!api|static|.\..|_next).)'
};

This is what I came up with:

import { i18nRouter } from 'next-i18n-router';
import { i18nConfig } from './i18nConfig';
import { NextRequest, NextResponse, userAgent } from 'next/server';

export function middleware(request: NextRequest, response: NextResponse) {
const url = request.nextUrl;
const { device } = userAgent(request);
const viewport = device.type === 'mobile' ? 'mobile' : 'desktop';

url.searchParams.set('viewport', viewport);

return i18nRouter(request, i18nConfig);
}

export const config = {
matcher: '/((?!api|static|.
\..|_next).)'
};

I would definitively prefer to keep the viewport searchParam out of the browser url but I am not sure if it's a good idea or not. For starters it will be hard for the next person to find where that searchParam is coming from. My questions is what complications I might expect if I proceed this way and/or are there any other (preferably better) solutions to that issue.

Thanks in advance!

6 Replies

@Acacia-ants Hello, to whoever might read this. I need to detect the device type so that I can render different components based on it. The docs that I found https://nextjs.org/docs/app/api-reference/functions/userAgent suggest that I should do it inside a middleware and rewrite the url. However, I already have a middleware that I set up for nexti18n and I am having a hard time making both work together. My current middleware: import { i18nRouter } from 'next-i18n-router'; import { i18nConfig } from './i18nConfig'; import { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { return i18nRouter(request, i18nConfig); } export const config = { matcher: '/((?!api|static|.*\\..*|_next).*)' }; This is what I came up with: import { i18nRouter } from 'next-i18n-router'; import { i18nConfig } from './i18nConfig'; import { NextRequest, NextResponse, userAgent } from 'next/server'; export function middleware(request: NextRequest, response: NextResponse) { const url = request.nextUrl; const { device } = userAgent(request); const viewport = device.type === 'mobile' ? 'mobile' : 'desktop'; url.searchParams.set('viewport', viewport); return i18nRouter(request, i18nConfig); } export const config = { matcher: '/((?!api|static|.*\\..*|_next).*)' }; I would definitively prefer to keep the viewport searchParam out of the browser url but I am not sure if it's a good idea or not. For starters it will be hard for the next person to find where that searchParam is coming from. My questions is what complications I might expect if I proceed this way and/or are there any other (preferably better) solutions to that issue. Thanks in advance!
I like to use the package [react-device-detect](https://www.npmjs.com/package/react-device-detect). Like that I can pass the userAgent from the server to it and the package tells me, if it's mobile or not, what kind of browser he's using and so much more
Acacia-antsOP
Thanks. The issue with the different packages is that they either don't work on the server, which will cause CLS, because on the server it will always default to either mobile or desktop. Or if they work on the server they use 'next/headers'. And if I understand correctly if I use 'next/headers' or 'next/cookies' I am basically opting out of caching, ISR and SSG from the call that uses them down the tree. The first time I need to detect the device is in the root layout. After that pretty much nothing gets cached. That being said it means that the only approach is to read the user agent directly from the request object. Prior versions had the request object in getServerSideProps for example, but that is no longer the case. I think the only place where one can get access to the request object is inside middleware, hence why they suggest that approach.
Acacia-antsOP
It's not about rendering it's about caching some requests and/or using ISR, SSG. Using next/cookies or next/headers opts out of those features because cookies and headers (auth header for example) are not stateless.
device detection on the server, which requires reading the userAgent header, needs something to be run at runtime, so it must be dynamically rendered (either by middleware rewrites or in dynamic server components). it cannot be statically rendered.
Acacia-antsOP
Mkay. Here is the hard truth. One can use search param to avoid reading headers but instead of NextResponse.rewrite (as suggested in nextjs docs) it should use NextResponse.redirect to keep the search param in the URL at all times. The reason is that when creating the data for useSearchParams client hook, nextjs reads the search params from the URL not from the execution context. Since rewrite will not append the search param to the URL it will remain hidden for the mentioned hook. Usually, this wouldn't be an issue but search params are not available inside layouts, and if they are not available in client components that are used inside layouts that means it won't be accessible in any layout context. So we are left with the other option which is redirect. Redirect will place the search param in the URL and we will be able to access it inside client components. This one however will be harder for maintenance. To avoid constant redirects each Link and usage of push and replace will have to persist the said search param. If not each time the middleware gets called and the search param is missing it will do a 307 redirect. As a last resort if anyone stumbles on the same particular issue you can always consider using build env variables and redirecting the mobile users to a subdomain (m.yourdomain.com).

I'll leave the thread open for a day or two, for someone to correct me if I wrote something wrong.

Note: SSG and ISR will be disabled even with the redirect, but you will be able to cache API calls