Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/app/(rewrites)/[[...path]]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {
DOCS_NEXT_DOMAIN,
LANDING_PAGE_DOMAIN,
LANDING_PAGE_FRAMER_DOMAIN,
replaceUrls,
} from '@/configs/domains'
import { ERROR_CODES } from '@/configs/logs'
import { logError } from '@/lib/clients/logger'
import { NextRequest } from 'next/server'

export const revalidate = 900

const REVALIDATE_TIME = 900 // 15 minutes ttl

export async function GET(request: NextRequest): Promise<Response> {
const url = new URL(request.url)
const requestHostname = url.hostname

const updateUrlHostname = (newHostname: string) => {
url.hostname = newHostname
url.port = ''
url.protocol = 'https'
}

if (url.pathname === '' || url.pathname === '/') {
updateUrlHostname(LANDING_PAGE_DOMAIN)
} else if (url.pathname.startsWith('/blog/category')) {
url.pathname = url.pathname.replace(/^\/blog/, '')
updateUrlHostname(LANDING_PAGE_DOMAIN)
} else {
const hostnameMap: Record<string, string> = {
'/terms': LANDING_PAGE_DOMAIN,
'/privacy': LANDING_PAGE_DOMAIN,
'/pricing': LANDING_PAGE_DOMAIN,
'/cookbook': LANDING_PAGE_DOMAIN,
'/changelog': LANDING_PAGE_DOMAIN,
'/blog': LANDING_PAGE_DOMAIN,
'/ai-agents': LANDING_PAGE_FRAMER_DOMAIN,
'/docs': DOCS_NEXT_DOMAIN,
}

const matchingPath = Object.keys(hostnameMap).find(
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
)

if (matchingPath) {
updateUrlHostname(hostnameMap[matchingPath])
}
}

if (url.hostname === requestHostname) {
url.pathname = '/not-found'
}

try {
const headers = new Headers(request.headers)
headers.delete('host') // prevent host header conflicts

const res = await fetch(url.toString(), {
headers,
redirect: 'follow',
cache: 'force-cache',
next: {
revalidate: REVALIDATE_TIME,
},
})

const contentType = res.headers.get('Content-Type')

if (contentType?.startsWith('text/html')) {
const html = await res.text()
const modifiedHtmlBody = replaceUrls(html, url.pathname, 'href="', '">')

// create new headers without content-encoding to ensure proper rendering
const newHeaders = new Headers(res.headers)
newHeaders.delete('content-encoding')

// add cache control headers if not already present
if (!newHeaders.has('cache-control')) {
newHeaders.set(
'cache-control',
`public, max-age=${REVALIDATE_TIME}, s-maxage=${REVALIDATE_TIME}, stale-while-revalidate=86400`
)
}

return new Response(modifiedHtmlBody, {
status: res.status,
headers: newHeaders,
})
}

return res
} catch (error) {
logError(ERROR_CODES.URL_REWRITE, error)

return new Response(
`Proxy Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
{
status: 502,
headers: { 'Content-Type': 'text/plain' },
}
)
}
}
5 changes: 5 additions & 0 deletions src/app/(rewrites)/not-found/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { notFound } from 'next/navigation'

export default function NotFound() {
throw notFound()
}
1 change: 0 additions & 1 deletion src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
LANDING_PAGE_FRAMER_DOMAIN,
} from '@/configs/domains'
import { BLOG_FRAMER_DOMAIN } from '@/configs/domains'
import { BASE_URL } from '@/configs/urls'

// Cache the sitemap for 24 hours (in seconds)
const SITEMAP_CACHE_TIME = 24 * 60 * 60
Expand Down
23 changes: 3 additions & 20 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,9 @@ import {
getAuthRedirect,
getUserSession,
handleTeamResolution,
handleUrlRewrites,
isDashboardRoute,
resolveTeamForDashboard,
} from './server/middleware'
import {
LANDING_PAGE_DOMAIN,
LANDING_PAGE_FRAMER_DOMAIN,
BLOG_FRAMER_DOMAIN,
DOCS_NEXT_DOMAIN,
} from '@/configs/domains'
import { PROTECTED_URLS } from './configs/urls'

// Main middleware function
Expand Down Expand Up @@ -51,17 +44,7 @@ export async function middleware(request: NextRequest) {
)
}

// 2. Handle URL rewrites first (early return for non-dashboard routes)
const rewriteResponse = await handleUrlRewrites(request, {
landingPage: LANDING_PAGE_DOMAIN,
landingPageFramer: LANDING_PAGE_FRAMER_DOMAIN,
blogFramer: BLOG_FRAMER_DOMAIN,
docsNext: DOCS_NEXT_DOMAIN,
})

if (rewriteResponse) return rewriteResponse

// 3. Refresh session and handle auth redirects
// 2. Refresh session and handle auth redirects
const { error, data } = await getUserSession(supabase)

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

// 4. Handle team resolution for all dashboard routes
// 3. Handle team resolution for all dashboard routes
const teamResult = await resolveTeamForDashboard(request, data.user.id)

// 5. Process team resolution result
// 4. Process team resolution result
return handleTeamResolution(request, response, teamResult)
} catch (error) {
// Return a basic response to avoid infinite loops
Expand Down
93 changes: 0 additions & 93 deletions src/server/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,99 +157,6 @@ export async function checkUserTeamAccess(
return hasAccess
}

/**
* Handles URL rewrites for static pages and content modifications.
*/
export const handleUrlRewrites = async (
request: NextRequest,
hostnames: {
landingPage: string
landingPageFramer: string
blogFramer: string
docsNext: string
}
): Promise<NextResponse | null> => {
if (request.method !== 'GET') {
return null
}

const url = new URL(request.nextUrl.toString())

// Helper function to update the URL for rewrite.
const updateUrlHostname = (
newHostname: string,
extraInfo?: Record<string, unknown>
) => {
url.hostname = newHostname
url.port = ''
url.protocol = 'https'
}

// 1. Rewrite the root path to the landing page.
if (url.pathname === '' || url.pathname === '/') {
updateUrlHostname(hostnames.landingPage)
}
// 2. Special case: /blog/category/xy -> landingPage with "/blog" prefix removed.
else if (url.pathname.startsWith('/blog/category')) {
const originalPath = url.pathname
// Remove the /blog prefix so that '/blog/category/xy' becomes '/category/xy'
url.pathname = url.pathname.replace(/^\/blog/, '')
updateUrlHostname(hostnames.landingPage)
}
// 3. Static page mappings.
else {
const hostnameMap: Record<string, string> = {
'/terms': hostnames.landingPage,
'/privacy': hostnames.landingPage,
'/pricing': hostnames.landingPage,
'/cookbook': hostnames.landingPage,
'/changelog': hostnames.landingPage,
'/blog': hostnames.landingPage,
'/ai-agents': hostnames.landingPageFramer,
'/docs': hostnames.docsNext,
}

const matchingPath = Object.keys(hostnameMap).find(
(path) => url.pathname === path || url.pathname.startsWith(path + '/')
)

if (matchingPath) {
updateUrlHostname(hostnameMap[matchingPath])
}
}

if (url.hostname === request.nextUrl.hostname) {
return null
}

try {
if (
url.hostname === LANDING_PAGE_DOMAIN ||
url.hostname === DOCS_NEXT_DOMAIN
) {
return NextResponse.rewrite(url.toString())
}

const headers = new Headers(request.headers)

const res = await fetch(url.toString(), {
...request,
headers,
redirect: 'follow',
})
const htmlBody = await res.text()
const modifiedHtmlBody = replaceUrls(htmlBody, url.pathname, 'href="', '">')

return new NextResponse(modifiedHtmlBody, {
status: res.status,
statusText: res.statusText,
headers: res.headers,
})
} catch (error) {
return null
}
}

// URL utility functions
export function isDashboardRoute(pathname: string): boolean {
return pathname.startsWith(PROTECTED_URLS.DASHBOARD)
Expand Down