fix(ssr): résoudre une base absolue pour les fetch API côté serveur (page édition)
This commit is contained in:
1 parent
8ff530bf99
commit
0ca2846f2b
3 files changed
+58
-1
No files matched your search
@@ -1,6 +1,9 @@
|
|||||||
# Variables exposées au client (préfixées NEXT_PUBLIC_)
|
# Variables exposées au client (préfixées NEXT_PUBLIC_)
|
||||||
# NEXT_PUBLIC_API_URL=http://localhost:3000
|
# NEXT_PUBLIC_API_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Base interne pour les fetch SSR (port interne du serveur Next.js, défaut 3000)
|
||||||
|
# API_BASE_URL=http://localhost:3000
|
||||||
|
|
||||||
# Implémentation repository active : mysql | mongo
|
# Implémentation repository active : mysql | mongo
|
||||||
DB_IMPL=mysql
|
DB_IMPL=mysql
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// @vitest-environment node
|
||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
const fetchMock = vi.fn()
|
||||||
|
|
||||||
|
async function loadModule(env?: Record<string, string>) {
|
||||||
|
vi.resetModules()
|
||||||
|
delete process.env.API_BASE_URL
|
||||||
|
if (env) Object.assign(process.env, env)
|
||||||
|
return (await import('./api-client')) as typeof import('./api-client')
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
global.fetch = fetchMock as unknown as typeof fetch
|
||||||
|
fetchMock.mockReset()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('cardsApi (SSR / Node)', () => {
|
||||||
|
it("résout une URL absolue sur localhost:3000 en l'absence de window", async () => {
|
||||||
|
const { cardsApi: api } = await loadModule()
|
||||||
|
fetchMock.mockResolvedValue(
|
||||||
|
new Response(JSON.stringify([]), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await api.list()
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
|
'http://localhost:3000/api/cards',
|
||||||
|
expect.objectContaining({ method: 'GET' }),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('respecte API_BASE_URL si elle est définie', async () => {
|
||||||
|
const { cardsApi: api } = await loadModule({ API_BASE_URL: 'http://api:9999' })
|
||||||
|
fetchMock.mockResolvedValue(
|
||||||
|
new Response(JSON.stringify([]), {
|
||||||
|
status: 200,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
await api.list()
|
||||||
|
expect(fetchMock).toHaveBeenCalledWith(
|
||||||
|
'http://api:9999/api/cards',
|
||||||
|
expect.objectContaining({ method: 'GET' }),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -14,10 +14,16 @@ interface ApiErrorPayload {
|
|||||||
message?: string
|
message?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Côté serveur (SSR/standalone), une URL relative n'est pas résolvable :
|
||||||
|
// on cible la propre API de l'application (port interne 3000). Côté client,
|
||||||
|
// l'URL relative est résolue par le navigateur.
|
||||||
|
const API_BASE_URL =
|
||||||
|
typeof window === 'undefined' ? (process.env.API_BASE_URL ?? 'http://localhost:3000') : ''
|
||||||
|
|
||||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||||
let res: Response
|
let res: Response
|
||||||
try {
|
try {
|
||||||
res = await fetch(path, {
|
res = await fetch(`${API_BASE_URL}${path}`, {
|
||||||
method: init?.method ?? 'GET',
|
method: init?.method ?? 'GET',
|
||||||
...init,
|
...init,
|
||||||
headers: { 'Content-Type': 'application/json', ...init?.headers },
|
headers: { 'Content-Type': 'application/json', ...init?.headers },
|
||||||
|
|||||||
Reference in new issue
Block a user