Next.js Discord

Discord Forum

NextJS Serves All Pages At Once in Middleware

Answered
beanbeanjuice posted this in #help-forum
Open in Discord
Avatar
I have a middleware here;

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
View full answer

4 Replies

Avatar
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

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.
Avatar
@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?
Avatar
If you want to exclude requests from prefetching, you need to increment the view from the client. Stuff like useEffect
Answer