Skip to content

Instantly share code, notes, and snippets.

@AdventureBear
Created January 30, 2025 18:55
Show Gist options
  • Save AdventureBear/87ebc3ffbd64d04a877d49043866fb30 to your computer and use it in GitHub Desktop.
Save AdventureBear/87ebc3ffbd64d04a877d49043866fb30 to your computer and use it in GitHub Desktop.
middleware
import { NextRequest, NextResponse } from 'next/server'
import { stateNames } from '@/app/lib/utils'
import cities from '@/data/cities'
import { slugify } from '@/app/lib/utils'
import { NextURL } from 'next/dist/server/web/next-url';
export async function middleware(req: NextRequest) {
const url = new NextURL(req.url)
const pathname = url.pathname
console.log('Middleware processing:', pathname)
// Ensure that the redirects don't affect the general /cities, /courses, or /states routes
if (pathname === '/cities' || pathname === '/courses' || pathname === '/states' || pathname === '/sitemap.xml' || pathname === '/api/sitemap.xml') {
return NextResponse.next(); // Allow the default page behavior (e.g., listing all cities, states, or courses)
}
// Skip if not a state-related path
if (!pathname.startsWith('/states/')) {
return NextResponse.next()
}
// Split pathname into segments
const segments = pathname.split('/').filter(Boolean)
// Get the state segment (should be first segment after /states/)
const stateSlug = segments[1]
const citySlug = segments[2];
const courseSlug = segments[3];
if (!stateSlug) {
return NextResponse.next()
}
// Check if it's a valid state - check both full name and code
const stateCode = Object.entries(stateNames).find(([code, name]) =>
slugify(name) === stateSlug.toLowerCase() || code === stateSlug.toUpperCase()
)?.[0]
console.log('State slug:', stateSlug)
console.log('State code found:', stateCode)
console.log('State name:', stateCode ? stateNames[stateCode] : 'not found')
// Redirect if state isn't properly slugified OR if any segment contains URL-encoded characters
if (stateCode && (
stateSlug !== slugify(stateNames[stateCode]) ||
segments.some(segment => segment.includes('%'))
)) {
const newSegments = segments.map((segment, index) => {
if (index === 1) {
// State segment
return slugify(stateNames[stateCode])
} else if (index === 2) {
// City segment
return slugify(decodeURIComponent(segment))
} else if (index === 3) {
// Course segment
return slugify(decodeURIComponent(segment))
}
return segment
})
const newPathname = '/' + newSegments.join('/')
console.log('Redirecting to:', newPathname)
return NextResponse.redirect(new URL(newPathname, req.url))
}
// If there's a city segment, validate it
// const citySlug = segments[2]
if (citySlug && stateCode) {
const decodedCitySlug = decodeURIComponent(citySlug)
const isValidCity = cities.some((city: string) => {
const [cityName, stateName] = city.split(', ')
return slugify(cityName) === slugify(decodedCitySlug) &&
stateCode ? slugify(stateName) === slugify(stateNames[stateCode]) : false
})
console.log(`Checking city: ${decodedCitySlug} for state: ${stateCode ? stateNames[stateCode] : 'unknown'}`)
console.log("Is Valid City: ", isValidCity)
// Check city is not in the database but a course is present, search for the course
if (!isValidCity && courseSlug) {
return NextResponse.next(); // Allow request if the course exists
}
if (!isValidCity) {
return NextResponse.redirect(new URL(`/states/${stateSlug}`, req.url))
}
}
console.log("URL is valid: ", req.url)
return NextResponse.next()
}
// Make sure middleware matches all relevant paths
// export const config = {
// matcher: ['/states/:path*']
// }
export const config = {
matcher: [
// Only match state/city/course patterns
'/states/:state/:city/:course*',
// Match state/city patterns
'/states/:state/:city',
// Match direct state patterns
'/states/:state'
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment