middleware.ts
1 import { HttpTypes } from "@medusajs/types" 2 import { NextRequest, NextResponse } from "next/server" 3 4 const BACKEND_URL = process.env.MEDUSA_BACKEND_URL 5 const PUBLISHABLE_API_KEY = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY 6 const DEFAULT_REGION = process.env.NEXT_PUBLIC_DEFAULT_REGION || "us" 7 8 const regionMapCache = { 9 regionMap: new Map<string, HttpTypes.StoreRegion>(), 10 regionMapUpdated: Date.now(), 11 } 12 13 async function getRegionMap(cacheId: string) { 14 const { regionMap, regionMapUpdated } = regionMapCache 15 16 if (!BACKEND_URL) { 17 throw new Error( 18 "Middleware.ts: Error fetching regions. Did you set up regions in your Medusa Admin and define a MEDUSA_BACKEND_URL environment variable? Note that the variable is no longer named NEXT_PUBLIC_MEDUSA_BACKEND_URL." 19 ) 20 } 21 22 if ( 23 !regionMap.keys().next().value || 24 regionMapUpdated < Date.now() - 3600 * 1000 25 ) { 26 // Fetch regions from Medusa. We can't use the JS client here because middleware is running on Edge and the client needs a Node environment. 27 const { regions } = await fetch(`${BACKEND_URL}/store/regions`, { 28 headers: { 29 "x-publishable-api-key": PUBLISHABLE_API_KEY!, 30 }, 31 next: { 32 revalidate: 3600, 33 tags: [`regions-${cacheId}`], 34 }, 35 cache: "force-cache", 36 }).then(async (response) => { 37 const json = await response.json() 38 39 if (!response.ok) { 40 throw new Error(json.message) 41 } 42 43 return json 44 }) 45 46 if (!regions?.length) { 47 throw new Error( 48 "No regions found. Please set up regions in your Medusa Admin." 49 ) 50 } 51 52 // Create a map of country codes to regions. 53 regions.forEach((region: HttpTypes.StoreRegion) => { 54 region.countries?.forEach((c) => { 55 regionMapCache.regionMap.set(c.iso_2 ?? "", region) 56 }) 57 }) 58 59 regionMapCache.regionMapUpdated = Date.now() 60 } 61 62 return regionMapCache.regionMap 63 } 64 65 /** 66 * Fetches regions from Medusa and sets the region cookie. 67 * @param request 68 * @param response 69 */ 70 async function getCountryCode( 71 request: NextRequest, 72 regionMap: Map<string, HttpTypes.StoreRegion | number> 73 ) { 74 try { 75 let countryCode 76 77 const vercelCountryCode = request.headers 78 .get("x-vercel-ip-country") 79 ?.toLowerCase() 80 81 const urlCountryCode = request.nextUrl.pathname.split("/")[1]?.toLowerCase() 82 83 if (urlCountryCode && regionMap.has(urlCountryCode)) { 84 countryCode = urlCountryCode 85 } else if (vercelCountryCode && regionMap.has(vercelCountryCode)) { 86 countryCode = vercelCountryCode 87 } else if (regionMap.has(DEFAULT_REGION)) { 88 countryCode = DEFAULT_REGION 89 } else if (regionMap.keys().next().value) { 90 countryCode = regionMap.keys().next().value 91 } 92 93 return countryCode 94 } catch (error) { 95 if (process.env.NODE_ENV === "development") { 96 console.error( 97 "Middleware.ts: Error getting the country code. Did you set up regions in your Medusa Admin and define a MEDUSA_BACKEND_URL environment variable? Note that the variable is no longer named NEXT_PUBLIC_MEDUSA_BACKEND_URL." 98 ) 99 } 100 } 101 } 102 103 /** 104 * Middleware to handle region selection and onboarding status. 105 */ 106 export async function middleware(request: NextRequest) { 107 let redirectUrl = request.nextUrl.href 108 109 let response = NextResponse.redirect(redirectUrl, 307) 110 111 let cacheIdCookie = request.cookies.get("_medusa_cache_id") 112 113 let cacheId = cacheIdCookie?.value || crypto.randomUUID() 114 115 const regionMap = await getRegionMap(cacheId) 116 117 const countryCode = regionMap && (await getCountryCode(request, regionMap)) 118 119 const urlHasCountryCode = 120 countryCode && request.nextUrl.pathname.split("/")[1].includes(countryCode) 121 122 // if one of the country codes is in the url and the cache id is set, return next 123 if (urlHasCountryCode && cacheIdCookie) { 124 return NextResponse.next() 125 } 126 127 // if one of the country codes is in the url and the cache id is not set, set the cache id and redirect 128 if (urlHasCountryCode && !cacheIdCookie) { 129 response.cookies.set("_medusa_cache_id", cacheId, { 130 maxAge: 60 * 60 * 24, 131 }) 132 133 return response 134 } 135 136 // check if the url is a static asset 137 if (request.nextUrl.pathname.includes(".")) { 138 return NextResponse.next() 139 } 140 141 const redirectPath = 142 request.nextUrl.pathname === "/" ? "" : request.nextUrl.pathname 143 144 const queryString = request.nextUrl.search ? request.nextUrl.search : "" 145 146 // If no country code is set, we redirect to the relevant region. 147 if (!urlHasCountryCode && countryCode) { 148 redirectUrl = `${request.nextUrl.origin}/${countryCode}${redirectPath}${queryString}` 149 response = NextResponse.redirect(`${redirectUrl}`, 307) 150 } 151 152 return response 153 } 154 155 export const config = { 156 matcher: [ 157 "/((?!api|_next/static|_next/image|favicon.ico|images|assets|png|svg|jpg|jpeg|gif|webp).*)", 158 ], 159 }