Here is the solution that I have. Due to the legacy product we have all of our URLs in one big Slug model in rails that maps URLs to models.
EG
[
{ 'Product 1' => '/product-1'},
{ 'Category 1' => '/category-1'}
}
Next.js it does not support sending a component from getStaticProps
, so our [...slug].js
winds up including every possible component (probably 50% of the total site code) leading to large code chunks where 80% of the code is not necessary for the page being rendered.
The solution was to use a custom middleware, but being the first time I've done something like this, I would like a review of my implementation. the pseudo code is:
- Find url lookup in cache
- If doesn't exist in cache, query API
- If url lookup successful, rewrite to the appropriate page
- EG type Product =>
/p/${url}
, type Category =>/c/${url}
- EG type Product =>
- If URL lookup unsuccessful, do nothing and let Next move on.
// middleware.js
import { NextResponse } from 'next/server'
import cache from 'memory-cache'
// Seachs an api to see if a particulary url slug has a specific type set.
// Then appends the path passed to next with a subfolder so that url can only
// have a specific component to render instead of loading them all in one [...slug].js
// or having to dynamically import them. It also uses a memory cache to not have to make
// round trips to the api on every request.
export async function middleware(request) {
const cacheTimeout = 3600
const knownBadPaths = ['_next']
if (knownBadPaths.some(element => request.nextUrl.pathname.includes(element))) {
let apiRes = cache.get(request.nextUrl.pathname)
if (apiRes === null) {
const nextSlug = request.nextUrl.pathname.substring(1).replaceAll('/', ',')
const apiReq = await fetch(`${process.env.NEXT_PUBLIC_API_ROOT}/public/url_slugs/${nextSlug}`)
apiRes = await apiReq.json()
cache.put(request.nextUrl.pathname, apiRes, cacheTimeout)
}
if (apiRes.type === 'Category') {
return NextResponse.rewrite(new URL(`/c${request.nextUrl.pathname}`, request.url))
} else if (apiRes.type === 'Product') {
return NextResponse.rewrite(new URL(`/p${request.nextUrl.pathname}`, request.url))
} //... elseif for any other paths that need to be rewritten for specific types
}
}
export const config = {
matcher: '/:path*',
}
apiRes = cache.get(request.nextUrl.pathname)
isnull
then is it possible that the call tofetch()
andapiRes = await apiReq.json()
thatapiRes
might still benull
or something that isn't an object? \$\endgroup\$apiRes
will never benull
. The server might return a404
,500
, etc. That is OK for this use case as this router only cares about pages that exist and routing them to the correct component. It will check at most every hour with the api to see if the errors have been resolved as the cache should timeout. \$\endgroup\$