Skip to content

Commit 650ea3e

Browse files
committed
chore: update from template
2 parents 9ef5001 + af2dae3 commit 650ea3e

File tree

2 files changed

+119
-33
lines changed

2 files changed

+119
-33
lines changed

app/(public)/[slug]/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { PostContent } from '@/components/features/post-content.component';
2-
import { postsQueryControllerFindAll } from '@/kodkafa/ssr/posts-query/posts-query';
3-
import { postsControllerFindOneBySlug } from '@/kodkafa/ssr/posts/posts';
42
import {
53
PageMetaDto,
64
PostDto,
75
PostsQueryControllerFindAll200,
86
} from '@/kodkafa/schemas';
9-
import { postsQueryControllerFindAllResponse } from '@/kodkafa/zod/kodkafaApi.zod';
10-
import { postsControllerFindOneBySlugResponse } from '@/kodkafa/zod/kodkafaApi.zod';
7+
import { postsQueryControllerFindAll } from '@/kodkafa/ssr/posts-query/posts-query';
8+
import { postsControllerFindOneBySlug } from '@/kodkafa/ssr/posts/posts';
9+
import {
10+
postsControllerFindOneBySlugResponse,
11+
postsQueryControllerFindAllResponse,
12+
} from '@/kodkafa/zod/kodkafaApi.zod';
1113
import { getApiDomain } from '@/lib/api/domain';
1214
import { fetchAndValidate } from '@/lib/api/fetch-and-validate';
1315
import { metadataGenerator } from '@/lib/seo/metadata.generator';

lib/api/ssr.mutator.ts

Lines changed: 113 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { getClientCredentialsToken } from '@/lib/auth/token-manager';
2+
13
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
24

35
export 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+
2132
function 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(/^\/api\//, '');
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

5957
export 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

Comments
 (0)