Skip to content

Commit 8b7d6e8

Browse files
Improve catch-all rewriter caching strategy (#36)
#34 introduced a catch-all route handler that acts as a fallback rewriter for our content pages. It had implementations of caching mechanisms, but these did not seem to work as expected in production. This pr introduces the use of `generateStaticParams` to cache route invocations for the paths present in our `sitemap` on build time, which results in instant server responses on our rewritten pages.
1 parent f378634 commit 8b7d6e8

File tree

5 files changed

+49
-31
lines changed

5 files changed

+49
-31
lines changed
Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import {
55
replaceUrls,
66
} from '@/configs/domains'
77
import { ERROR_CODES } from '@/configs/logs'
8-
import { logError } from '@/lib/clients/logger'
98
import { NextRequest } from 'next/server'
9+
import sitemap from '@/app/sitemap'
10+
import { BASE_URL } from '@/configs/urls'
1011

1112
export const revalidate = 900
13+
export const dynamic = 'force-static'
1214

1315
const REVALIDATE_TIME = 900 // 15 minutes ttl
1416

@@ -48,21 +50,24 @@ export async function GET(request: NextRequest): Promise<Response> {
4850
}
4951
}
5052

51-
if (url.hostname === requestHostname) {
52-
url.pathname = '/not-found'
53-
}
54-
5553
try {
56-
const headers = new Headers(request.headers)
57-
headers.delete('host') // prevent host header conflicts
58-
59-
const res = await fetch(url.toString(), {
60-
headers,
54+
// if hostname did not change, we want to make sure it does not cache the route based on the build times hostname (127.0.0.1:3000)
55+
const fetchUrl =
56+
url.hostname === requestHostname
57+
? `${BASE_URL}/not-found`
58+
: url.toString()
59+
60+
const res = await fetch(fetchUrl, {
61+
headers: new Headers(request.headers),
6162
redirect: 'follow',
62-
cache: 'force-cache',
63-
next: {
64-
revalidate: REVALIDATE_TIME,
65-
},
63+
// if the hostname is the same, we don't want to cache the response, since it will not be available in build time
64+
...(url.hostname === requestHostname
65+
? { cache: 'no-store' }
66+
: {
67+
next: {
68+
revalidate: REVALIDATE_TIME,
69+
},
70+
}),
6671
})
6772

6873
const contentType = res.headers.get('Content-Type')
@@ -75,14 +80,6 @@ export async function GET(request: NextRequest): Promise<Response> {
7580
const newHeaders = new Headers(res.headers)
7681
newHeaders.delete('content-encoding')
7782

78-
// add cache control headers if not already present
79-
if (!newHeaders.has('cache-control')) {
80-
newHeaders.set(
81-
'cache-control',
82-
`public, max-age=${REVALIDATE_TIME}, s-maxage=${REVALIDATE_TIME}, stale-while-revalidate=86400`
83-
)
84-
}
85-
8683
return new Response(modifiedHtmlBody, {
8784
status: res.status,
8885
headers: newHeaders,
@@ -91,7 +88,7 @@ export async function GET(request: NextRequest): Promise<Response> {
9188

9289
return res
9390
} catch (error) {
94-
logError(ERROR_CODES.URL_REWRITE, error)
91+
console.error(ERROR_CODES.URL_REWRITE, error)
9592

9693
return new Response(
9794
`Proxy Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
@@ -102,3 +99,17 @@ export async function GET(request: NextRequest): Promise<Response> {
10299
)
103100
}
104101
}
102+
103+
export async function generateStaticParams() {
104+
const sitemapEntries = await sitemap()
105+
106+
const slugs = sitemapEntries.map((entry) => {
107+
const url = new URL(entry.url)
108+
const pathname = url.pathname
109+
const pathSegments = pathname.split('/').filter((segment) => segment !== '')
110+
111+
return { slug: pathSegments.length > 0 ? pathSegments : undefined }
112+
})
113+
114+
return slugs
115+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { notFound } from 'next/navigation'
1+
import NotFound from '@/ui/not-found'
22

3-
export default function NotFound() {
4-
throw notFound()
3+
export const dynamic = 'force-static'
4+
5+
export default function NotFoundShell() {
6+
return <NotFound />
57
}

src/configs/urls.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export const PROTECTED_URLS = {
2121
KEYS: (teamId: string) => `/dashboard/${teamId}/keys`,
2222
}
2323

24-
export const BASE_URL = process.env.VERCEL_URL
25-
? `https://${process.env.VERCEL_URL}`
24+
export const BASE_URL = process.env.VERCEL_ENV
25+
? process.env.VERCEL_ENV === 'production'
26+
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
27+
: `https://${process.env.VERCEL_BRANCH_URL}`
2628
: 'http://localhost:3000'

src/lib/env.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ export const serverSchema = z.object({
55
COOKIE_ENCRYPTION_KEY: z.string().min(32),
66

77
BILLING_API_URL: z.string().url().optional(),
8-
VERCEL_URL: z.string().optional(),
98
DEVELOPMENT_INFRA_API_DOMAIN: z.string().optional(),
109
SENTRY_AUTH_TOKEN: z.string().optional(),
1110
ZEROBOUNCE_API_KEY: z.string().optional(),
11+
VERCEL_ENV: z.enum(['production', 'preview', 'development']).optional(),
12+
VERCEL_URL: z.string().optional(),
13+
VERCEL_PROJECT_PRODUCTION_URL: z.string().optional(),
14+
VERCEL_BRANCH_URL: z.string().optional(),
1215
})
1316

1417
export const clientSchema = z.object({

src/middleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ export const config = {
7878
* - favicon.ico (favicon file)
7979
* - images - .svg, .png, .jpg, .jpeg, .gif, .webp
8080
* - api routes
81-
* Feel free to modify this pattern to include more paths.
81+
* - paths handled by the catchall route.ts (terms, privacy, pricing, cookbook, changelog, blog, ai-agents, docs)
8282
*/
83-
'/((?!_next/static|_next/image|favicon.ico|api/|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
83+
'/((?!_next/static|_next/image|favicon.ico|api/|terms|privacy|pricing|cookbook|changelog|blog|ai-agents|docs|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
8484
],
8585
}

0 commit comments

Comments
 (0)