NextJS Serves All Pages At Once in Middleware
Answered
beanbeanjuice posted this in #help-forum
I have a middleware here;
The analytics function seems to run on all available pages when visiting any of the pages. This only occurs on the production build.
For example, I visit
On the development build, visiting
import {NextRequest, NextResponse} from "next/server";
import {analytics} from "@/app/(api)/api/_analytics/Analytics";
const ipToCountryCode = new Map<string, string>();
async function getCountry(req: NextRequest) {
const ip = req.headers.get('x-forwarded-for')?.split(',')[0] || '0.0.0.0'; // Get the first IP in case of proxy chains
if (ipToCountryCode.has(ip)) {
return ipToCountryCode.get(ip);
}
// Use an external API like ipstack or ipinfo to get the geo info.
// Example using ipinfo.io:
const res = await fetch(`https://ipinfo.io/${ip}/json`);
const data = await res.json();
const country = data.country || 'US'; // Default to 'US' if country is not found
ipToCountryCode.set(ip, country);
return country;
}
function isBot(userAgent: string) {
const botKeywords = [
'bot', 'crawl', 'slurp', 'spider', 'WhatsApp', 'TelegramBot', 'Slackbot',
'Viber', 'Discordbot', 'SkypeUriPreview'
];
if (botKeywords.some(keyword => userAgent.toLowerCase().includes(keyword.toLowerCase()))) {
console.log(`Bot (${userAgent}) detected. Not counting page views.`);
return true;
}
return false;
}
export default async function middleware(req: NextRequest, res: NextResponse) {
if (!isBot(req.headers.get('user-agent') || '')) {
getCountry(req).then(async (country) => {
try {
await analytics.track('pageview', {
page: req.nextUrl.pathname,
country: country
});
} catch (_) { }
});
}
return NextResponse.next();
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
'/((?!api|_next/static|_next/image|icons|favicon|assets|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
The analytics function seems to run on all available pages when visiting any of the pages. This only occurs on the production build.
For example, I visit
http://somewebsite.com
, a "page view" will show up for /
/about
/gallery
... and so on. Clicking on any of the sub-pages through the nav, does not re-trigger the middleware.On the development build, visiting
http://localhost:3000
, a page view will show up for /
. Clicking on any of the sub-pages, like /about
, will create a page view for /about
(which is what I want). Why is this the case, and how can I fix it?Answered by joulev
If you want to exclude requests from prefetching, you need to increment the view from the client. Stuff like useEffect
4 Replies
So, I see it's because of prefetching. Is there a way to keep the same performance from prefetching, while also not counting all the views at once?
I found this: https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy
This "works" but now, when I navigate to a sub-page, the middleware does not execute. Reloading, however, does execute the middleware.
import {NextRequest, NextResponse} from "next/server";
import {analytics} from "@/app/(api)/api/_analytics/Analytics";
const ipToCountryCode = new Map<string, string>();
async function getCountry(req: NextRequest) {
const ip = req.headers.get('x-forwarded-for')?.split(',')[0] || '0.0.0.0'; // Get the first IP in case of proxy chains
if (ipToCountryCode.has(ip)) {
return ipToCountryCode.get(ip);
}
// Use an external API like ipstack or ipinfo to get the geo info.
// Example using ipinfo.io:
const res = await fetch(`https://ipinfo.io/${ip}/json`);
const data = await res.json();
const country = data.country || 'US'; // Default to 'US' if country is not found
ipToCountryCode.set(ip, country);
return country;
}
function isBot(userAgent: string) {
const botKeywords = [
'bot', 'crawl', 'slurp', 'spider', 'WhatsApp', 'TelegramBot', 'Slackbot',
'Viber', 'Discordbot', 'SkypeUriPreview'
];
if (botKeywords.some(keyword => userAgent.toLowerCase().includes(keyword.toLowerCase()))) {
console.log(`Bot (${userAgent}) detected. Not counting page views.`);
return true;
}
return false;
}
export default async function middleware(req: NextRequest, res: NextResponse) {
if (!isBot(req.headers.get('user-agent') || '')) {
getCountry(req).then(async (country) => {
try {
await analytics.track('pageview', {
page: req.nextUrl.pathname,
country: country
});
} catch (_) { }
});
}
return NextResponse.next();
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
{
source: '/((?!api|_next/static|_next/image|icons|favicon|assets|favicon.ico|sitemap.xml|robots.txt).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
}
],
}
This "works" but now, when I navigate to a sub-page, the middleware does not execute. Reloading, however, does execute the middleware.
@beanbeanjuice So, I see it's because of prefetching. Is there a way to keep the same performance from prefetching, while also not counting all the views at once?
If you want to exclude requests from prefetching, you need to increment the view from the client. Stuff like useEffect
Answer
@joulev If you want to exclude requests from prefetching, you need to increment the view from the client. Stuff like useEffect
I see... no way around it. Thank you so much!