forked from LibreChat-AI/librechat.ai
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.ts
More file actions
86 lines (74 loc) · 3.52 KB
/
Copy pathproxy.ts
File metadata and controls
86 lines (74 loc) · 3.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { NextRequest, NextResponse, type NextFetchEvent } from 'next/server'
import { createI18nMiddleware } from 'fumadocs-core/i18n/middleware'
import { i18n, LOCALE_COOKIE } from '@/lib/i18n'
const i18nMiddleware = createI18nMiddleware(i18n)
function isMarkdownPreferred(request: NextRequest): boolean {
const accept = request.headers.get('accept') ?? ''
return accept.includes('text/markdown')
}
/**
* The reader's preferred site language: an explicit choice (the LOCALE_COOKIE
* set by the language switcher) wins; otherwise the best `Accept-Language`
* match among the locales we build; falling back to the default language.
*/
function preferredLocale(request: NextRequest): string {
const cookie = request.cookies.get(LOCALE_COOKIE)?.value
if (cookie && i18n.languages.includes(cookie)) return cookie
const header = request.headers.get('accept-language')
if (!header) return i18n.defaultLanguage
const ranked = header
.split(',')
.map((part) => {
const [tag, q] = part.trim().split(';q=')
return { base: tag.split('-')[0].toLowerCase(), q: q ? Number(q) : 1 }
})
.sort((a, b) => b.q - a.q)
for (const { base } of ranked) {
if (i18n.languages.includes(base)) return base
}
return i18n.defaultLanguage
}
export default function proxy(request: NextRequest, event: NextFetchEvent) {
const { pathname } = request.nextUrl
// Leave raw markdown routes to next.config rewrites; don't localize them.
// Must run before content negotiation: an Accept: text/markdown request to a
// .md/.mdx URL needs to keep its suffix so the next.config rewrite can strip
// it, otherwise the route handler gets a slug that still ends in .md and 404s.
if (pathname.endsWith('.md') || pathname.endsWith('.mdx')) return NextResponse.next()
// Serve raw markdown for content-negotiated requests (LLM/agent tooling).
if (isMarkdownPreferred(request) && pathname.startsWith('/docs')) {
const rest = pathname.slice('/docs'.length)
return NextResponse.rewrite(new URL(`/llms.mdx/docs${rest}`, request.nextUrl))
}
// Browser-language auto-detection, scoped to the prefix-less home page. A
// reader whose browser prefers a translated locale is forwarded to its
// landing page (`/<locale>`); English readers and explicit English choosers
// stay on `/`. Deliberately limited to `/`: the content routes (/docs, /blog,
// …) are shared-CDN-cached, and an Accept-Language redirect there would be
// cached and served to every reader regardless of their language.
//
// The decision depends on Cookie + Accept-Language, so BOTH outcomes must stay
// out of any shared cache: a cached English `/` 200 (not just the redirect)
// would otherwise be replayed to a German/Spanish/… visitor without the proxy
// re-running, bypassing detection. `Vary` alone isn't enough — Cloudflare
// ignores it — so mark both responses `private, no-store`.
if (pathname === '/') {
const locale = preferredLocale(request)
let response: NextResponse
if (locale === i18n.defaultLanguage) {
response = NextResponse.next()
} else {
const url = request.nextUrl.clone()
url.pathname = `/${locale}`
response = NextResponse.redirect(url, 307)
}
response.headers.set('Cache-Control', 'private, no-store')
response.headers.set('Vary', 'Cookie, Accept-Language')
return response
}
// Locale detection + default-locale rewriting for the docs.
return i18nMiddleware(request, event)
}
export const config = {
matcher: ['/', '/docs/:path*', '/(zh|es|fr|de|ja)/docs/:path*'],
}