Merge pull request #18 from Sosokker/main

Get middleware and auth logic from backend
This commit is contained in:
Sirin Puenggun 2024-09-10 01:42:09 +07:00 committed by GitHub
commit a9aedcf3c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 345 additions and 109 deletions

View File

@ -1,2 +1,4 @@
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
NEXT_PUBLIC_AUTH_GOOGLE_ID=your-google-id
NEXT_PUBLIC_AUTH_GOOGLE_SECRET=your-google-secret

114
package-lock.json generated
View File

@ -10,15 +10,19 @@
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.45.1",
"@supabase/ssr": "^0.4.1",
"@supabase/supabase-js": "^2.45.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.2.0",
"lucide-react": "^0.428.0",
"next": "14.2.5",
"next-themes": "^0.3.0",
"react": "^18",
"react-countup": "^6.5.3",
"react-dom": "^18",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
@ -820,6 +824,29 @@
}
}
},
"node_modules/@radix-ui/react-progress": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.0.tgz",
"integrity": "sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==",
"dependencies": {
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-primitive": "2.0.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz",
@ -850,6 +877,28 @@
}
}
},
"node_modules/@radix-ui/react-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz",
"integrity": "sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==",
"dependencies": {
"@radix-ui/react-primitive": "2.0.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
@ -1069,9 +1118,9 @@
}
},
"node_modules/@supabase/ssr": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.4.0.tgz",
"integrity": "sha512-6WS3NUvHDhCPAFN2kJ79AQDO8+M9fJ7y2fYpxgZqIuJEpnnGsHDNnB5Xnv8CiaJIuRU+0pKboy62RVZBMfZ0Lg==",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.4.1.tgz",
"integrity": "sha512-000i7y4ITXjXU0T1JytZYU33VbUNklX9YN47hCweaLKsTBAEigJJJCeq3G+/IiwEggBt58Vu0KQ3UGXON7OmDQ==",
"dependencies": {
"cookie": "^0.6.0"
},
@ -1083,24 +1132,24 @@
}
},
"node_modules/@supabase/storage-js": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.6.0.tgz",
"integrity": "sha512-REAxr7myf+3utMkI2oOmZ6sdplMZZ71/2NEIEMBZHL9Fkmm3/JnaOZVSRqvG4LStYj2v5WhCruCzuMn6oD/Drw==",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.0.tgz",
"integrity": "sha512-iZenEdO6Mx9iTR6T7wC7sk6KKsoDPLq8rdu5VRy7+JiT1i8fnqfcOr6mfF2Eaqky9VQzhP8zZKQYjzozB65Rig==",
"dependencies": {
"@supabase/node-fetch": "^2.6.14"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.45.1",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.1.tgz",
"integrity": "sha512-/PVe3lXmalazD8BGMIoI7+ttvT1mLXy13lNcoAPtjP1TDDY83g8csZbVR6l+0/RZtvJxl3LGXfTJT4bjWgC5Nw==",
"version": "2.45.2",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.45.2.tgz",
"integrity": "sha512-kJKY3ISFusVKQWCP8Kqo20Ebxy2WLp6Ry/Suco0aQsPXH7bvn7clswsdhcfcH/5Tr0MYz/jcCjF0n/27SetiCw==",
"dependencies": {
"@supabase/auth-js": "2.64.4",
"@supabase/functions-js": "2.4.1",
"@supabase/node-fetch": "2.6.15",
"@supabase/postgrest-js": "1.15.8",
"@supabase/realtime-js": "2.10.2",
"@supabase/storage-js": "2.6.0"
"@supabase/storage-js": "2.7.0"
}
},
"node_modules/@swc/counter": {
@ -1830,6 +1879,11 @@
"node": ">= 0.6"
}
},
"node_modules/countup.js": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.8.0.tgz",
"integrity": "sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ=="
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -2050,6 +2104,31 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/embla-carousel": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.2.0.tgz",
"integrity": "sha512-rf2GIX8rab9E6ZZN0Uhz05746qu2KrDje9IfFyHzjwxLwhvGjUt6y9+uaY1Sf+B0OPSa3sgas7BE2hWZCtopTA=="
},
"node_modules/embla-carousel-react": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.2.0.tgz",
"integrity": "sha512-dWqbmaEBQjeAcy/EKrcAX37beVr0ubXuHPuLZkx27z58V1FIvRbbMb4/c3cLZx0PAv/ofngX2QFrwUB+62SPnw==",
"dependencies": {
"embla-carousel": "8.2.0",
"embla-carousel-reactive-utils": "8.2.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.1 || ^18.0.0"
}
},
"node_modules/embla-carousel-reactive-utils": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.2.0.tgz",
"integrity": "sha512-ZdaPNgMydkPBiDRUv+wRIz3hpZJ3LKrTyz+XWi286qlwPyZFJDjbzPBiXnC3czF9N/nsabSc7LTRvGauUzwKEg==",
"peerDependencies": {
"embla-carousel": "8.2.0"
}
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -4470,6 +4549,17 @@
"node": ">=0.10.0"
}
},
"node_modules/react-countup": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz",
"integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==",
"dependencies": {
"countup.js": "^2.8.0"
},
"peerDependencies": {
"react": ">= 16.3.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",

View File

@ -15,8 +15,8 @@
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.45.1",
"@supabase/ssr": "^0.4.1",
"@supabase/supabase-js": "^2.45.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.2.0",

View File

@ -0,0 +1,24 @@
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get("code");
// if "next" is in param, use it in the redirect URL
const next = searchParams.get("next") ?? "/";
if (code) {
const supabase = createSupabaseClient();
const { error } = await supabase.auth.exchangeCodeForSession(code);
if (!error) {
return NextResponse.redirect(`${origin}${next}`);
}
}
// return the user to an error page with instructions
return NextResponse.redirect(`${origin}/auth/error`);
}

View File

@ -0,0 +1,3 @@
export default function AuthError() {
return <div>Authentication Error</div>;
}

View File

@ -2,6 +2,9 @@ import Image from "next/image";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { LoginButton } from "@/components/auth/loginButton";
import { LogoutButton } from "@/components/auth/logoutButton";
export default function Login() {
return (
<div
@ -16,14 +19,8 @@ export default function Login() {
</CardHeader>
<CardContent className="flex flex-col gap-y-2 mx-28">
<p className="self-center font-semibold text-slate-800">Continue With</p>
<Button className="bg-foreground gap-2 rounded-xl">
<Image src={"/logo/google.svg"} width={30} height={30} alt={"Google"} />
Continue with Google
</Button>
<Button className="gap-2 rounded-xl">
<Image src={"/logo/facebook.svg"} width={30} height={30} alt={"Google"} />
Continue with Facebook
</Button>
<LoginButton />
<LogoutButton />
</CardContent>
<CardFooter className="text-xs justify-center">
By signing up, you agree to the Terms of Service and acknowledge youve read our Privacy Policy.

View File

@ -19,7 +19,7 @@ export default function Home() {
<p>Together, we turn ideas into impact.</p>
</span>
<Button className="font-bold mt-4">
<Link href="/login">Start Investing</Link>
<Link href="/">Start Investing</Link>
</Button>
</span>
</div>
@ -79,10 +79,40 @@ export default function Home() {
<p className="text-lg">The deals attracting the most interest right now</p>
</span>
<div className="grid grid-cols-4 gap-4">
<BusinessCard name={"NVDA"} description={"Founded in 1993, NVIDIA is a key innovator of computer graphics and AI technology"} joinDate={"December 2021"} location={"Bangkok, Thailand"} tags={null} />
<BusinessCard name={"Apple Inc."} description={"Founded in 1976, Apple Inc. is a leading innovator in consumer electronics, software, and online services, known for products like the iPhone, MacBook, and the App Store."} joinDate={"February 2020"} location={"Cupertino, California, USA"} tags={null} />
<BusinessCard name={"Google LLC"} description={"Founded in 1998, Google LLC specializes in internet-related services and products, including search engines, online advertising, cloud computing, and the Android operating system."} joinDate={"April 2019"} location={"Mountain View, California, USA"} tags={null} />
<BusinessCard name={"Microsoft Corporation"} description={"Founded in 1975, Microsoft Corporation is a multinational technology company that develops, manufactures, and licenses software, hardware, and services, including Windows, Office, and Azure."} joinDate={"January 2018"} location={""} tags={null} />
<BusinessCard
name={"NVDA"}
description={"Founded in 1993, NVIDIA is a key innovator of computer graphics and AI technology"}
joinDate={"December 2021"}
location={"Bangkok, Thailand"}
tags={null}
/>
<BusinessCard
name={"Apple Inc."}
description={
"Founded in 1976, Apple Inc. is a leading innovator in consumer electronics, software, and online services, known for products like the iPhone, MacBook, and the App Store."
}
joinDate={"February 2020"}
location={"Cupertino, California, USA"}
tags={null}
/>
<BusinessCard
name={"Google LLC"}
description={
"Founded in 1998, Google LLC specializes in internet-related services and products, including search engines, online advertising, cloud computing, and the Android operating system."
}
joinDate={"April 2019"}
location={"Mountain View, California, USA"}
tags={null}
/>
<BusinessCard
name={"Microsoft Corporation"}
description={
"Founded in 1975, Microsoft Corporation is a multinational technology company that develops, manufactures, and licenses software, hardware, and services, including Windows, Office, and Azure."
}
joinDate={"January 2018"}
location={""}
tags={null}
/>
</div>
<div className="self-center py-5">
<Button>

View File

@ -0,0 +1,25 @@
"use client";
import Image from "next/image";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
import { Button } from "@/components/ui/button";
export function LoginButton(props: { nextUrl?: string }) {
const supabase = createSupabaseClient();
const handleLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${location.origin}/auth/callback?next=${props.nextUrl || ""}`,
},
});
};
return (
<Button className="bg-foreground gap-2 rounded-xl" onClick={handleLogin}>
<Image src={"/logo/google.svg"} width={30} height={30} alt={"Google"} />
Continue with Google
</Button>
);
}

View File

@ -0,0 +1,16 @@
"use client";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
import { useRouter } from "next/navigation";
export function LogoutButton() {
const supabase = createSupabaseClient();
const router = useRouter();
const handleLogout = async () => {
await supabase.auth.signOut();
router.push("/");
};
return <button onClick={handleLogout}>Logout</button>;
}

View File

@ -31,31 +31,27 @@ const landings = [
route: "/crm-landing",
},
];
const ListItem = React.forwardRef<
React.ElementRef<"a">,
React.ComponentPropsWithoutRef<"a">
>(({ className, title, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className
)}
{...props}
>
<div className="text-sm font-medium leading-none">{title}</div>
<hr />
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
{children}
</p>
</a>
</NavigationMenuLink>
</li>
);
});
const ListItem = React.forwardRef<React.ElementRef<"a">, React.ComponentPropsWithoutRef<"a">>(
({ className, title, children, ...props }, ref) => {
return (
<li>
<NavigationMenuLink asChild>
<a
ref={ref}
className={cn(
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
className
)}
{...props}>
<div className="text-sm font-medium leading-none">{title}</div>
<hr />
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">{children}</p>
</a>
</NavigationMenuLink>
</li>
);
}
);
ListItem.displayName = "ListItem";
export function UnsignedNav() {
@ -90,8 +86,7 @@ export function UnsignedNav() {
<Link
className="flex-none text-xl font-semibold dark:text-white focus:outline-none focus:opacity-80"
href="/"
aria-label="Brand"
>
aria-label="Brand">
<span className="inline-flex items-center gap-x-2 text-xl font-semibold dark:text-white">
<Image src="./logo.svg" alt="logo" width={50} height={50} />
B2DVentures
@ -103,17 +98,11 @@ export function UnsignedNav() {
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger className="text-base">
Businesses
</NavigationMenuTrigger>
<NavigationMenuTrigger className="text-base">Businesses</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] ">
{businessComponents.map((component) => (
<ListItem
key={component.title}
title={component.title}
href={component.href}
>
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
@ -122,17 +111,11 @@ export function UnsignedNav() {
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className="text-base font-medium ">
Projects
</NavigationMenuTrigger>
<NavigationMenuTrigger className="text-base font-medium ">Projects</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] ">
{projectComponents.map((component) => (
<ListItem
key={component.title}
title={component.title}
href={component.href}
>
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
@ -141,17 +124,11 @@ export function UnsignedNav() {
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger className="text-base">
Blogs
</NavigationMenuTrigger>
<NavigationMenuTrigger className="text-base">Blogs</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] ">
{blogComponents.map((component) => (
<ListItem
key={component.title}
title={component.title}
href={component.href}
>
<ListItem key={component.title} title={component.title} href={component.href}>
{component.description}
</ListItem>
))}
@ -160,10 +137,7 @@ export function UnsignedNav() {
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
className="text-base font-medium"
href="docs"
>
<NavigationMenuLink className="text-base font-medium" href="docs">
Docs
</NavigationMenuLink>
</NavigationMenuItem>
@ -177,7 +151,7 @@ export function UnsignedNav() {
<div className="flex gap-2 pl-2">
<ThemeToggle />
<Separator orientation="vertical" className="mx-3" />
<Link href="/login">
<Link href="/auth">
<Button variant="secondary" className="border-2 border-border">
Login
</Button>

View File

@ -0,0 +1,5 @@
import { createBrowserClient } from "@supabase/ssr";
export function createSupabaseClient() {
return createBrowserClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!);
}

View File

@ -0,0 +1,57 @@
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value));
supabaseResponse = NextResponse.next({
request,
});
cookiesToSet.forEach(({ name, value, options }) => supabaseResponse.cookies.set(name, value, options));
},
},
}
);
// IMPORTANT: Avoid writing any logic between createServerClient and
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.
const {
data: { user },
} = await supabase.auth.getUser();
if (!user && !request.nextUrl.pathname.startsWith("/auth") && !request.nextUrl.pathname.startsWith("/auth")) {
// no user, potentially respond by redirecting the user to the login page
const url = request.nextUrl.clone();
url.pathname = "/auth";
return NextResponse.redirect(url);
}
// IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
// creating a new response object with NextResponse.next() make sure to:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
// 3. Change the myNewResponse object to fit your needs, but avoid changing
// the cookies!
// 4. Finally:
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!
return supabaseResponse;
}

View File

@ -1,25 +0,0 @@
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
export const createClient = () => {
const cookieStore = cookies();
return createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, {
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options);
});
} catch (error) {
// The `set` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
});
};

View File

@ -0,0 +1,19 @@
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
export function createSupabaseClient() {
const cookieStore = cookies();
return createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, {
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options));
} catch {}
},
},
});
}

19
src/middleware.ts Normal file
View File

@ -0,0 +1,19 @@
import { type NextRequest } from "next/server";
import { updateSession } from "@/lib/supabase/middleware";
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* Feel free to modify this pattern to include more paths.
*/
"/((?!_next/static|_next/image|$|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};