1+ import { getClientCredentialsToken } from '@/lib/auth/token-manager' ;
2+
13export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' ;
24
35export type QueryParams = Record <
@@ -18,6 +20,15 @@ export interface FetchOptions<TBody>
1820 cache ?: RequestCache ;
1921}
2022
23+ /**
24+ * Type helper to pass next.revalidate and cache through RequestInit
25+ * Used when calling generated SSR functions that accept RequestInit
26+ */
27+ export type FetchOptionsWithNext = RequestInit & {
28+ next ?: { revalidate ?: number | false ; tags ?: string [ ] } ;
29+ cache ?: RequestCache ;
30+ } ;
31+
2132function buildQuery ( params ?: QueryParams ) : string {
2233 if ( ! params ) return '' ;
2334 const usp = new URLSearchParams ( ) ;
@@ -29,47 +40,122 @@ function buildQuery(params?: QueryParams): string {
2940 return q . length > 0 ? `?${ q } ` : '' ;
3041}
3142
32- function getBaseUrl ( ) : string {
33- // Build time ve runtime için base URL
34- // Vercel'de VERCEL_URL kullanılır, local'de localhost
35- if ( process . env . VERCEL_URL ) {
36- return `https://${ process . env . VERCEL_URL } ` ;
37- }
38- if ( process . env . NEXT_PUBLIC_BASE_URL ) {
39- return process . env . NEXT_PUBLIC_BASE_URL ;
40- }
41- // Default: localhost (development ve build time için)
42- return 'http://localhost:3000' ;
43+ function isProxyUrl ( url : string ) : boolean {
44+ return url . startsWith ( '/api/' ) ;
4345}
4446
45- function toAbsoluteUrl ( url : string ) : string {
46- // Zaten mutlak URL ise olduğu gibi döndür
47- if ( url . startsWith ( 'http://' ) || url . startsWith ( 'https://' ) ) {
48- return url ;
47+ function convertProxyUrlToBackendUrl ( proxyUrl : string ) : string {
48+ // /api/posts/goker.me/by-slug/goker -> posts/goker.me/by-slug/goker
49+ const path = proxyUrl . replace ( / ^ \/ a p i \/ / , '' ) ;
50+ const apiBase = process . env . KODKAFA_API_URL ;
51+ if ( ! apiBase ) {
52+ throw new Error ( 'KODKAFA_API_URL environment variable is required' ) ;
4953 }
50- // Relative URL'yi mutlak URL'ye çevir
51- const baseUrl = getBaseUrl ( ) ;
52- const cleanUrl = url . startsWith ( '/' ) ? url : `/${ url } ` ;
53-
54- console . log ( 'baseUrl' , baseUrl ) ;
55- console . log ( 'cleanUrl' , cleanUrl ) ;
56- return `${ baseUrl } ${ cleanUrl } ` ;
54+ return `${ apiBase . replace ( / \/ + $ / , '' ) } /${ path } ` ;
5755}
5856
5957export async function ssrMutator < TResponse , TBody = unknown > (
6058 url : string ,
6159 options ?: FetchOptions < TBody >
6260) : Promise < TResponse > {
6361 const query = buildQuery ( options ?. params ) ;
62+ const method = options ?. method ?? ( options ?. body ? 'POST' : 'GET' ) ;
63+
64+ // SSR context'te proxy URL'lerini backend'e direkt çevir
65+ // Bu, production build'de kendi sunucusuna istek yapma sorununu çözer
66+ if ( isProxyUrl ( url ) ) {
67+ const backendUrl = convertProxyUrlToBackendUrl ( url ) ;
68+ const targetUrl = `${ backendUrl } ${ query } ` ;
69+
70+ // SSR'da client credentials token kullan
71+ const headers = new Headers ( ) ;
72+ if ( options ?. headers ) {
73+ if ( options . headers instanceof Headers ) {
74+ options . headers . forEach ( ( value , key ) => {
75+ headers . set ( key , value ) ;
76+ } ) ;
77+ } else if ( Array . isArray ( options . headers ) ) {
78+ options . headers . forEach ( ( [ key , value ] ) => {
79+ headers . set ( key , value ) ;
80+ } ) ;
81+ } else {
82+ Object . entries ( options . headers ) . forEach ( ( [ key , value ] ) => {
83+ if ( value ) {
84+ headers . set (
85+ key ,
86+ Array . isArray ( value ) ? value . join ( ', ' ) : String ( value )
87+ ) ;
88+ }
89+ } ) ;
90+ }
91+ }
92+
93+ if ( ! headers . has ( 'authorization' ) ) {
94+ // Lazy token loading - only fetch when needed
95+ // This prevents DYNAMIC_SERVER_USAGE errors during static generation
96+ const token = await getClientCredentialsToken ( ) ;
97+ headers . set ( 'authorization' , `Bearer ${ token } ` ) ;
98+ }
99+
100+ // Determine cache strategy for static generation compatibility:
101+ // - If cache is explicitly set, use it
102+ // - If next.revalidate is provided, use 'default' to allow static generation
103+ // - Otherwise, use 'default' to allow static generation (not 'no-store')
104+ // Note: 'default' allows Next.js to statically generate pages even for external API calls
105+ // 'no-store' forces dynamic rendering which breaks static generation
106+ let cacheStrategy : RequestCache = 'default' ;
107+
108+ if ( options ?. cache !== undefined ) {
109+ // Explicit cache option takes precedence
110+ cacheStrategy = options . cache ;
111+ } else if ( options ?. next ?. revalidate !== undefined ) {
112+ // If revalidate is set, use 'default' to allow static generation
113+ cacheStrategy = 'default' ;
114+ }
115+ // Default to 'default' (not 'no-store') to allow static generation
116+
117+ const init : RequestInit = {
118+ method,
119+ headers,
120+ body : options ?. body ? JSON . stringify ( options . body ) : undefined ,
121+ cache : cacheStrategy ,
122+ } ;
123+
124+ const res = await fetch ( targetUrl , init ) ;
125+ const ct = res . headers . get ( 'content-type' ) ?? '' ;
126+
127+ if ( ct . includes ( 'application/json' ) ) {
128+ const json = await res . json ( ) ;
129+ return {
130+ status : res . status ,
131+ data : json ,
132+ } as TResponse ;
133+ }
134+
135+ if ( ! res . ok ) {
136+ const text = await res . text ( ) ;
137+ throw new Error ( text || `HTTP ${ res . status } ` ) ;
138+ }
139+
140+ return {
141+ status : res . status ,
142+ data : await res . text ( ) ,
143+ } as unknown as TResponse ;
144+ }
145+
146+ // Proxy URL değilse, mevcut davranışı koru (relative URL için)
147+ // Bu durumda Next.js'in internal fetch'i kullanılır
148+ const absoluteUrl =
149+ url . startsWith ( 'http://' ) || url . startsWith ( 'https://' )
150+ ? url
151+ : url . startsWith ( '/' )
152+ ? url
153+ : `/${ url } ` ;
64154
65- console . log ( 'url' , url ) ;
66- const absoluteUrl = toAbsoluteUrl ( url ) ;
67155 const init : RequestInit = {
68- method : options ?. method ?? ( options ?. body ? 'POST' : 'GET' ) ,
156+ method,
69157 headers : options ?. headers ,
70158 body : options ?. body ? JSON . stringify ( options . body ) : undefined ,
71- // SSR tarafında token yönetimi BFF'de; cache politika kontrolü burada:
72- // cache: 'no-store',
73159 credentials : 'include' ,
74160 } ;
75161
@@ -82,14 +168,12 @@ export async function ssrMutator<TResponse, TBody = unknown>(
82168
83169 if ( ct . includes ( 'application/json' ) ) {
84170 const json = await res . json ( ) ;
85- // Response'u { status, data } formatına çevir
86171 return {
87172 status : res . status ,
88173 data : json ,
89174 } as TResponse ;
90175 }
91176
92- // JSON olmayan response'lar için
93177 if ( ! res . ok ) {
94178 const text = await res . text ( ) ;
95179 throw new Error ( text || `HTTP ${ res . status } ` ) ;
0 commit comments