Skip to content

Commit 0bd5855

Browse files
Refactor content page rewrites (#34)
1 parent f2d47bf commit 0bd5855

File tree

5 files changed

+112
-114
lines changed

5 files changed

+112
-114
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
DOCS_NEXT_DOMAIN,
3+
LANDING_PAGE_DOMAIN,
4+
LANDING_PAGE_FRAMER_DOMAIN,
5+
replaceUrls,
6+
} from '@/configs/domains'
7+
import { ERROR_CODES } from '@/configs/logs'
8+
import { logError } from '@/lib/clients/logger'
9+
import { NextRequest } from 'next/server'
10+
11+
export const revalidate = 900
12+
13+
const REVALIDATE_TIME = 900 // 15 minutes ttl
14+
15+
export async function GET(request: NextRequest): Promise<Response> {
16+
const url = new URL(request.url)
17+
const requestHostname = url.hostname
18+
19+
const updateUrlHostname = (newHostname: string) => {
20+
url.hostname = newHostname
21+
url.port = ''
22+
url.protocol = 'https'
23+
}
24+
25+
if (url.pathname === '' || url.pathname === '/') {
26+
updateUrlHostname(LANDING_PAGE_DOMAIN)
27+
} else if (url.pathname.startsWith('/blog/category')) {
28+
url.pathname = url.pathname.replace(/^\/blog/, '')
29+
updateUrlHostname(LANDING_PAGE_DOMAIN)
30+
} else {
31+
const hostnameMap: Record<string, string> = {
32+
'/terms': LANDING_PAGE_DOMAIN,
33+
'/privacy': LANDING_PAGE_DOMAIN,
34+
'/pricing': LANDING_PAGE_DOMAIN,
35+
'/cookbook': LANDING_PAGE_DOMAIN,
36+
'/changelog': LANDING_PAGE_DOMAIN,
37+
'/blog': LANDING_PAGE_DOMAIN,
38+
'/ai-agents': LANDING_PAGE_FRAMER_DOMAIN,
39+
'/docs': DOCS_NEXT_DOMAIN,
40+
}
41+
42+
const matchingPath = Object.keys(hostnameMap).find(
43+
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
44+
)
45+
46+
if (matchingPath) {
47+
updateUrlHostname(hostnameMap[matchingPath])
48+
}
49+
}
50+
51+
if (url.hostname === requestHostname) {
52+
url.pathname = '/not-found'
53+
}
54+
55+
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,
61+
redirect: 'follow',
62+
cache: 'force-cache',
63+
next: {
64+
revalidate: REVALIDATE_TIME,
65+
},
66+
})
67+
68+
const contentType = res.headers.get('Content-Type')
69+
70+
if (contentType?.startsWith('text/html')) {
71+
const html = await res.text()
72+
const modifiedHtmlBody = replaceUrls(html, url.pathname, 'href="', '">')
73+
74+
// create new headers without content-encoding to ensure proper rendering
75+
const newHeaders = new Headers(res.headers)
76+
newHeaders.delete('content-encoding')
77+
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+
86+
return new Response(modifiedHtmlBody, {
87+
status: res.status,
88+
headers: newHeaders,
89+
})
90+
}
91+
92+
return res
93+
} catch (error) {
94+
logError(ERROR_CODES.URL_REWRITE, error)
95+
96+
return new Response(
97+
`Proxy Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
98+
{
99+
status: 502,
100+
headers: { 'Content-Type': 'text/plain' },
101+
}
102+
)
103+
}
104+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { notFound } from 'next/navigation'
2+
3+
export default function NotFound() {
4+
throw notFound()
5+
}

src/app/sitemap.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
LANDING_PAGE_FRAMER_DOMAIN,
1616
} from '@/configs/domains'
1717
import { BLOG_FRAMER_DOMAIN } from '@/configs/domains'
18-
import { BASE_URL } from '@/configs/urls'
1918

2019
// Cache the sitemap for 24 hours (in seconds)
2120
const SITEMAP_CACHE_TIME = 24 * 60 * 60

src/middleware.ts

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,9 @@ import {
44
getAuthRedirect,
55
getUserSession,
66
handleTeamResolution,
7-
handleUrlRewrites,
87
isDashboardRoute,
98
resolveTeamForDashboard,
109
} from './server/middleware'
11-
import {
12-
LANDING_PAGE_DOMAIN,
13-
LANDING_PAGE_FRAMER_DOMAIN,
14-
BLOG_FRAMER_DOMAIN,
15-
DOCS_NEXT_DOMAIN,
16-
} from '@/configs/domains'
1710
import { PROTECTED_URLS } from './configs/urls'
1811

1912
// Main middleware function
@@ -51,17 +44,7 @@ export async function middleware(request: NextRequest) {
5144
)
5245
}
5346

54-
// 2. Handle URL rewrites first (early return for non-dashboard routes)
55-
const rewriteResponse = await handleUrlRewrites(request, {
56-
landingPage: LANDING_PAGE_DOMAIN,
57-
landingPageFramer: LANDING_PAGE_FRAMER_DOMAIN,
58-
blogFramer: BLOG_FRAMER_DOMAIN,
59-
docsNext: DOCS_NEXT_DOMAIN,
60-
})
61-
62-
if (rewriteResponse) return rewriteResponse
63-
64-
// 3. Refresh session and handle auth redirects
47+
// 2. Refresh session and handle auth redirects
6548
const { error, data } = await getUserSession(supabase)
6649

6750
// Handle authentication redirects
@@ -73,10 +56,10 @@ export async function middleware(request: NextRequest) {
7356
return response
7457
}
7558

76-
// 4. Handle team resolution for all dashboard routes
59+
// 3. Handle team resolution for all dashboard routes
7760
const teamResult = await resolveTeamForDashboard(request, data.user.id)
7861

79-
// 5. Process team resolution result
62+
// 4. Process team resolution result
8063
return handleTeamResolution(request, response, teamResult)
8164
} catch (error) {
8265
// Return a basic response to avoid infinite loops

src/server/middleware.ts

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -157,99 +157,6 @@ export async function checkUserTeamAccess(
157157
return hasAccess
158158
}
159159

160-
/**
161-
* Handles URL rewrites for static pages and content modifications.
162-
*/
163-
export const handleUrlRewrites = async (
164-
request: NextRequest,
165-
hostnames: {
166-
landingPage: string
167-
landingPageFramer: string
168-
blogFramer: string
169-
docsNext: string
170-
}
171-
): Promise<NextResponse | null> => {
172-
if (request.method !== 'GET') {
173-
return null
174-
}
175-
176-
const url = new URL(request.nextUrl.toString())
177-
178-
// Helper function to update the URL for rewrite.
179-
const updateUrlHostname = (
180-
newHostname: string,
181-
extraInfo?: Record<string, unknown>
182-
) => {
183-
url.hostname = newHostname
184-
url.port = ''
185-
url.protocol = 'https'
186-
}
187-
188-
// 1. Rewrite the root path to the landing page.
189-
if (url.pathname === '' || url.pathname === '/') {
190-
updateUrlHostname(hostnames.landingPage)
191-
}
192-
// 2. Special case: /blog/category/xy -> landingPage with "/blog" prefix removed.
193-
else if (url.pathname.startsWith('/blog/category')) {
194-
const originalPath = url.pathname
195-
// Remove the /blog prefix so that '/blog/category/xy' becomes '/category/xy'
196-
url.pathname = url.pathname.replace(/^\/blog/, '')
197-
updateUrlHostname(hostnames.landingPage)
198-
}
199-
// 3. Static page mappings.
200-
else {
201-
const hostnameMap: Record<string, string> = {
202-
'/terms': hostnames.landingPage,
203-
'/privacy': hostnames.landingPage,
204-
'/pricing': hostnames.landingPage,
205-
'/cookbook': hostnames.landingPage,
206-
'/changelog': hostnames.landingPage,
207-
'/blog': hostnames.landingPage,
208-
'/ai-agents': hostnames.landingPageFramer,
209-
'/docs': hostnames.docsNext,
210-
}
211-
212-
const matchingPath = Object.keys(hostnameMap).find(
213-
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
214-
)
215-
216-
if (matchingPath) {
217-
updateUrlHostname(hostnameMap[matchingPath])
218-
}
219-
}
220-
221-
if (url.hostname === request.nextUrl.hostname) {
222-
return null
223-
}
224-
225-
try {
226-
if (
227-
url.hostname === LANDING_PAGE_DOMAIN ||
228-
url.hostname === DOCS_NEXT_DOMAIN
229-
) {
230-
return NextResponse.rewrite(url.toString())
231-
}
232-
233-
const headers = new Headers(request.headers)
234-
235-
const res = await fetch(url.toString(), {
236-
...request,
237-
headers,
238-
redirect: 'follow',
239-
})
240-
const htmlBody = await res.text()
241-
const modifiedHtmlBody = replaceUrls(htmlBody, url.pathname, 'href="', '">')
242-
243-
return new NextResponse(modifiedHtmlBody, {
244-
status: res.status,
245-
statusText: res.statusText,
246-
headers: res.headers,
247-
})
248-
} catch (error) {
249-
return null
250-
}
251-
}
252-
253160
// URL utility functions
254161
export function isDashboardRoute(pathname: string): boolean {
255162
return pathname.startsWith(PROTECTED_URLS.DASHBOARD)

0 commit comments

Comments
 (0)