Next.js Discord

Discord Forum

How to make global middleware and regular middleware in NextJS 14?

Unanswered
Bigheaded ant posted this in #help-forum
Open in Discord
Avatar
Bigheaded antOP
Hello everyone, can anyone help me out how we can make the global and regular middleware in NextJS. So I have a case like there are two middlewares one is withHeadersMiddleware (global) and two is authMiddleware (only in specific pages applied). How can I set each of this middleware?

48 Replies

Avatar
Bigheaded antOP
This is my code

import { NextRequest, NextResponse } from 'next/server';
import { getCookies } from './services/cookies';
import { toast } from './hooks/use-toast';
    
export async function middleware(request: NextRequest) {
    const { pathname } = request.nextUrl;
    
    // Fetch user data and token from cookies
    const userCookie = await getCookies("__u__");
    const tokenCookie = await getCookies("__tk__");

    // Create new headers to pass additional data
    const requestHeaders = new Headers(request.headers);

    // Append URL and pathname to headers
    requestHeaders.set('x-url', request.url);
    requestHeaders.set('x-pathname', pathname);
    console.log(request.url, pathname)

    // Redirect to login if accessing dashboard without authentication
    if (pathname.startsWith("/dashboard") && (!userCookie || !tokenCookie)) {
        toast({
            title: "Akses ditolak",
            description: "Harap login terlebih dahulu",
            variant: "destructive",
        });
        return NextResponse.redirect(new URL('/auth/login', request.url));
    }

    // Redirect to dashboard if already logged in and trying to access login page
    if ((
        pathname.startsWith("/auth/login") || 
        pathname.startsWith("/auth/register")
    ) && userCookie && tokenCookie) {
        return NextResponse.redirect(new URL('/dashboard', request.url));
    }

    // Continue request with updated headers
    return NextResponse.next({
        request: {
            headers: requestHeaders,
        },
    });
}

export const config = {
    matcher: [
        "/dashboard",
        "/dashboard/profile",
        "/dashboard/vitae/create",
        "/dashboard/vitae/:id/edit",
        "/auth/login",
        "/auth/register"
    ]
}
The case seems the same from this thread. But I'm not using next-auth for the authentication.
https://stackoverflow.com/questions/77977609/next-middleware-combination-next-auth-and-next-international
Avatar
@Bigheaded ant Hello everyone, can anyone help me out how we can make the global and regular middleware in NextJS. So I have a case like there are two middlewares one is withHeadersMiddleware (global) and two is authMiddleware (only in specific pages applied). How can I set each of this middleware?
Avatar
nextjs provides only one middleware right now. You can still split your middleware in multiple functions and call those different functions like withHeadersMIdleware and authMiddleware. So just call the functions inside this one middlelware and you are good to go 👍
something like this.

const authPages = [
    "dashboard": {
      title: {
        isDynamic: false,
        static: "Dashboard",
        withSitename: true
      },
      description: {
        isDynamic: false,
        static: "It's dashboard page.",
        withSitename: true,
      },
      isAuthMiddleware: true,
    },
    ...another pages
]
Avatar
@Bigheaded ant something like this. const authPages = [ "dashboard": { title: { isDynamic: false, static: "Dashboard", withSitename: true }, description: { isDynamic: false, static: "It's dashboard page.", withSitename: true, }, isAuthMiddleware: true, }, ...another pages ]
Avatar
yea.. I wouldn't do it like you did, as it's waaay to much effort for me, but you can do it like that. I would do it like
const protectedRoutes = ["/abc", "/def", "/admin"]
Avatar
@B33fb0n3 yea.. I wouldn't do it like you did, as it's waaay to much effort for me, but you can do it like that. I would do it like tsx const protectedRoutes = ["/abc", "/def", "/admin"]
Avatar
Bigheaded antOP
I did that to support seo, so all-in-one, started from the title page, meta data, middleware, etc. Then what I should do, just call one file from that.
I named this file as seo.ts
I set the seo in the layout only and not from every pages.
Avatar
@Bigheaded ant you can either set it on the page itself (using the metadata object) or if you want to have the same metadata everywhere, you can use your layout
Avatar
@B33fb0n3 <@1079709612078534676> you can either set it on the page itself (using the metadata object) or if you want to have the same metadata everywhere, you can use your layout
Avatar
Bigheaded antOP
Yeah that's what I am doing now. I did something like this in my layout app.tsx to make the title and metadata becomes dynamic in every pages.

// app.tsx
import { Inter } from "next/font/google";
import "./globals.css";
import { Toaster } from "@/components/ui/toaster";
import { headers } from "next/headers";
import { generatePageMetadata } from "@/lib/get-page-metadata";
import { SessionProvider } from "@/app/providers/session-provider"; // Import dari file yang baru dibuat

const inter = Inter({
  subsets: ["latin"],
  display: "swap",
});

// Dynamic metadata handler
export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  // Ambil URL path dari headers API
  const currentPathname = headers().get("x-pathname") || "/"; // Default "/" jika kosong
  const slug = currentPathname === "/" ? "index" : currentPathname.slice(1); // Ambil slug dari path

  // Generate metadata berdasarkan slug
  const metadata = await generatePageMetadata({ slug });

  return metadata; // Return hasil metadata
}

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <head>
        <script defer src="/plugins/observer-tailwindcss-intersect.min.js" />
      </head>
      <body className={`${inter.className} antialiased`}>
        {/* Bungkus children dengan SessionProvider */}
        <SessionProvider>
          {children}
        </SessionProvider>
        <Toaster />
      </body>
    </html>
  );
}
Avatar
@Bigheaded ant Yeah that's what I am doing now. I did something like this in my layout app.tsx to make the title and metadata becomes dynamic in every pages. // app.tsx import { Inter } from "next/font/google"; import "./globals.css"; import { Toaster } from "@/components/ui/toaster"; import { headers } from "next/headers"; import { generatePageMetadata } from "@/lib/get-page-metadata"; import { SessionProvider } from "@/app/providers/session-provider"; // Import dari file yang baru dibuat const inter = Inter({ subsets: ["latin"], display: "swap", }); // Dynamic metadata handler export async function generateMetadata({ params, }: { params: Promise<{ slug: string }>; }) { // Ambil URL path dari headers API const currentPathname = headers().get("x-pathname") || "/"; // Default "/" jika kosong const slug = currentPathname === "/" ? "index" : currentPathname.slice(1); // Ambil slug dari path // Generate metadata berdasarkan slug const metadata = await generatePageMetadata({ slug }); return metadata; // Return hasil metadata } export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <head> <script defer src="/plugins/observer-tailwindcss-intersect.min.js" /> </head> <body className={`${inter.className} antialiased`}> {/* Bungkus children dengan SessionProvider */} <SessionProvider> {children} </SessionProvider> <Toaster /> </body> </html> ); }
Avatar
eww.. why do you need the pathname? Normally your layout is inside a specific path and then you already know the path automatically. Also, how does your generatePageMetadata function look like?
Avatar
@B33fb0n3 eww.. why do you need the pathname? Normally your layout is inside a specific path and then you already know the path automatically. Also, how does your `generatePageMetadata` function look like?
Avatar
Bigheaded antOP
It to generates the page metadata like title, description, etc.

// @ts-nocheck
import { TITLE_PAGES } from "./constants/seo";

export async function generatePageMetadata({
  slug,
}: {
  slug: string;
}): Promise<any> {
    const siteName = process?.env.APP_NAME ?? "My App"; // Replace with your site's name
    // Get page-specific metadata
    const pageSEO = TITLE_PAGES[slug];

    if (!pageSEO) {
        return {
            title: "Page Not Found",
            description: "The requested page does not exist.",
        };
    }

    // Title generation
    const title = pageSEO?.title?.withSitename
        ? `${pageSEO?.title?.static} | ${siteName}`
        : pageSEO?.title?.static;

    // Description handling
    const description = pageSEO?.description?.isDynamic
        ? "Dynamic description here" // Dynamically fetch or process if needed
        : pageSEO?.description?.static || "";

    // Icon handling
    const icon = pageSEO?.icons?.icon
        ? pageSEO?.icons?.icon : "";

    return {
        title,
        description,
        openGraph: {
        title: pageSEO?.title?.withSitename
            ? `${pageSEO?.title?.static} | ${siteName}`
            : pageSEO?.title?.static,
        description: pageSEO?.og?.description?.static || "",
        },
        twitter: {
            title: pageSEO?.twitter?.title?.withSitename
                ? `${pageSEO?.twitter?.title?.static} | ${siteName}`
                : pageSEO?.twitter?.title?.static,
            description: pageSEO?.twitter?.description?.static || "",
        },
        icon
    };
}
Avatar
@B33fb0n3 oh wow.. while reading that much code I forgot wheres the actual problem right now. Can you clarify your issue?
Avatar
Bigheaded antOP
Initially, it was the auth middleware that I wanted to separate it because I wanted this auth middleware to only be implemented on certain pages. Then there is the global middleware, which is middleware for inserting URLs/pathnames into request headers, which later uses the URL/pathname to detect whether the URL on the current page is the same as the URL as in SEO or not, if yes then all the metadata or title on the page will use one of these SEO.
So for example, I visited page /auth/login then in the current page will use the seo that related to that path.
and for checking the auth middleware I set a data called isAuthMiddleware to check whether this page is protected or not.
Avatar
ok got it. Why don't you create an auth middleware and "delete" the other middlware and set your page metadata like everyone else does inside the page.tsx via the metadata object?
So I don't should set the middleware in my SEO?
Avatar
@Bigheaded ant So I don't should set the middleware in my SEO?
Avatar
yea, dont set SEO through middleware. Set it inside your page.tsx (then you already have the pathname as well and it will be invisible, when he is unauthenticated)
Avatar
@B33fb0n3 yea, dont set SEO through middleware. Set it inside your page.tsx (then you already have the pathname as well and it will be invisible, when he is unauthenticated)
Avatar
Bigheaded antOP
U mean, it looks like this if I implemented it in one of pages.

import Banner from "@/components/banner";
import ShieldUserIcon from "@/components/icon/shield-user-icon";
import CreateFormJobs from "./_components/form";

export const metadata = {
    isAuthMiddleware: true
}

export default function Page(){
    return (
        <div className="container my-5 flex flex-col gap-5">
        </div>
    )    
}
Avatar
@Bigheaded ant U mean, it looks like this if I implemented it in one of pages. import Banner from "@/components/banner"; import ShieldUserIcon from "@/components/icon/shield-user-icon"; import CreateFormJobs from "./_components/form"; export const metadata = { isAuthMiddleware: true } export default function Page(){ return ( <div className="container my-5 flex flex-col gap-5"> </div> ) }
Avatar
no.
1. Create a middleware with matcher configured to all protected routes. Inside this middleware you check the auth
2. In each page.tsx you create a metadata object for your title, description, ...:
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
}
 
export default function Page() {}
Avatar
@Bigheaded ant But still the data of isAuthMiddleware need to put it in the SEO file, right? to check whether this path or route is need a authentication or not.
Avatar
the authMiddleware should check which pages are protected.
1. Do that via the matcher (see attached).
2. Remove the withHeadersMiddleware middleware as you dont need it.
3. Go into your protected page.tsx file and add metadata.
4. See that it works 🙂
Image
Why I should delete withHeadersMiddleware?
I want the title,metadata, etc manage based in one file only. So I don't need to search the specific page if I want to change the title or metadata as you know it's confusing while search the specific page in Next because their name must be 'page'.
That's why I make the generatePageMetadata become dynamic in the layout.
Avatar
@Bigheaded ant you are right, you would then need to create multiple metadata objects in each of your page.tsx and you can still reference to your seo file like:
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: seoFile.adminPage.title,
  description: '...',
}
 
export default function Page() {}

When using the headers() dynamic function, it will turn your page into a dynamic page that would make your app slower when it can be fast
Avatar
@B33fb0n3 the authMiddleware should check which pages are protected. 1. Do that via the matcher (see attached). 2. Remove the `withHeadersMiddleware` middleware as you dont need it. 3. Go into your protected page.tsx file and add metadata. 4. See that it works 🙂
Avatar
Bigheaded antOP
But how about the page that needs authentication, do I still need to put the data of 'isAuthMiddleware' in the SEO file or just use the matcher?
But I still don't know how to set the path that needs an authentication in matcher.
Because there's a page that for the guest too. And I'm afraid when I'm using the matcher, this page for the guest will be impacted by the auth middleware.
Avatar
@Bigheaded ant But how about the page that needs authentication, do I still need to put the data of 'isAuthMiddleware' in the SEO file or just use the matcher?
Avatar
the SEO part is fully independend from the auth part. So for the SEO part you use your metadata object (maybe reference to your file)
And for your auth part, you use your middleware (or also inside the page)
Avatar
@Bigheaded ant what do you mean middleware inside the page?
Avatar
you can check the auth also inside each page
Avatar
Bigheaded antOP
But how about the method that I'm using now? is it also a good practice?
or there's still some drawback.
Avatar
@B33fb0n3 you can check the auth also inside each page
Avatar
Bigheaded antOP
If it's fine, I will still use the way as now.
Avatar
@Bigheaded ant But how about the method that I'm using now? is it also a good practice?
Avatar
I don't think it's a good pratice, as it seems to be unflexable (as you see now) and makes your page slower (as it uses a dynamic function). Of course you can still continue your path, but earlier or later you will run into the next issue. I would change it, like I mentioned
I need your advice.
Image
Avatar
@Bigheaded ant Sir I want to ask again, how about my new middleware for protecting the auth middleware for the pages and api routes.
Avatar
boddy, you already got my advice about that. I would use a middleware that checks only the auth (or when guests are allowed I would check it on page) and settings all the SEO related metadata inside the page itself