feat(frontend): support separate browser and server API base URLs
Add NUXT_SERVER_API_BASE_URL for internal server-side API requests Update API and i18n services to select base URL by execution context
This commit is contained in:
@@ -9,8 +9,10 @@ COPY frontend ./frontend
|
||||
COPY system-wordings.ts ./system-wordings.ts
|
||||
|
||||
ARG NUXT_PUBLIC_API_BASE_URL=http://localhost:3001
|
||||
ARG NUXT_SERVER_API_BASE_URL=http://localhost:3001
|
||||
ARG NUXT_PUBLIC_SITE_URL=https://pokopiawiki.tootaio.com
|
||||
ENV NUXT_PUBLIC_API_BASE_URL=$NUXT_PUBLIC_API_BASE_URL
|
||||
ENV NUXT_SERVER_API_BASE_URL=$NUXT_SERVER_API_BASE_URL
|
||||
ENV NUXT_PUBLIC_SITE_URL=$NUXT_PUBLIC_SITE_URL
|
||||
RUN pnpm --filter @pokopia/frontend build
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ export default defineNuxtConfig({
|
||||
css: ['~/src/styles/main.css'],
|
||||
compatibilityDate: '2026-05-06',
|
||||
runtimeConfig: {
|
||||
serverApiBaseUrl:
|
||||
process.env.NUXT_SERVER_API_BASE_URL ??
|
||||
process.env.NUXT_API_BASE_URL ??
|
||||
process.env.NUXT_PUBLIC_API_BASE_URL ??
|
||||
process.env.VITE_API_BASE_URL ??
|
||||
'http://localhost:3001',
|
||||
public: {
|
||||
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL ?? process.env.VITE_API_BASE_URL ?? 'http://localhost:3001',
|
||||
siteUrl: normalizeSiteUrl(process.env.NUXT_PUBLIC_SITE_URL ?? process.env.VITE_SITE_URL)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { setSystemWordingsApiBaseUrl } from '../src/i18n';
|
||||
import { setSystemWordingsApiBaseUrls } from '../src/i18n';
|
||||
import { setConfiguredSiteUrl } from '../src/seo';
|
||||
import { setApiBaseUrl } from '../src/services/api';
|
||||
import { setApiBaseUrls } from '../src/services/api';
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const config = useRuntimeConfig();
|
||||
setApiBaseUrl(config.public.apiBaseUrl);
|
||||
setSystemWordingsApiBaseUrl(config.public.apiBaseUrl);
|
||||
const apiBaseUrls = {
|
||||
browser: config.public.apiBaseUrl,
|
||||
server: config.serverApiBaseUrl
|
||||
};
|
||||
|
||||
setApiBaseUrls(apiBaseUrls);
|
||||
setSystemWordingsApiBaseUrls(apiBaseUrls);
|
||||
setConfiguredSiteUrl(config.public.siteUrl);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,8 @@ import { createI18n } from 'vue-i18n';
|
||||
import { defaultLocale, systemWordingMessages, type SystemWordingTree } from '../../system-wordings';
|
||||
|
||||
export { defaultLocale } from '../../system-wordings';
|
||||
let apiBaseUrl = 'http://localhost:3001';
|
||||
let browserApiBaseUrl = 'http://localhost:3001';
|
||||
let serverApiBaseUrl = 'http://localhost:3001';
|
||||
const localeStorageKey = 'pokopia_locale';
|
||||
const localeChangeEvent = 'pokopia-locale-change';
|
||||
|
||||
@@ -26,9 +27,31 @@ export const i18n = createI18n({
|
||||
});
|
||||
|
||||
export function setSystemWordingsApiBaseUrl(value: unknown): void {
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
apiBaseUrl = value.trim();
|
||||
setSystemWordingsApiBaseUrls({ browser: value, server: value });
|
||||
}
|
||||
|
||||
export function setSystemWordingsApiBaseUrls(value: { browser?: unknown; server?: unknown }): void {
|
||||
const browserBaseUrl = normalizeApiBaseUrl(value.browser);
|
||||
const serverBaseUrl = normalizeApiBaseUrl(value.server);
|
||||
|
||||
if (browserBaseUrl) {
|
||||
browserApiBaseUrl = browserBaseUrl;
|
||||
}
|
||||
if (serverBaseUrl) {
|
||||
serverApiBaseUrl = serverBaseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeApiBaseUrl(value: unknown): string | null {
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
return value.trim().replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function activeApiBaseUrl(): string {
|
||||
return typeof window === 'undefined' ? serverApiBaseUrl : browserApiBaseUrl;
|
||||
}
|
||||
|
||||
function readStoredLocale(): string {
|
||||
@@ -87,7 +110,7 @@ export async function loadSystemWordings(locale = getCurrentLocale(), force = fa
|
||||
|
||||
const loadPromise = (async () => {
|
||||
try {
|
||||
const response = await fetch(`${apiBaseUrl}/api/system-wordings?locale=${encodeURIComponent(targetLocale)}`);
|
||||
const response = await fetch(`${activeApiBaseUrl()}/api/system-wordings?locale=${encodeURIComponent(targetLocale)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`System wordings failed (${response.status})`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getCurrentLocale } from '../i18n';
|
||||
|
||||
let apiBaseUrl = 'http://localhost:3001';
|
||||
let browserApiBaseUrl = 'http://localhost:3001';
|
||||
let serverApiBaseUrl = 'http://localhost:3001';
|
||||
const authTokenKey = 'pokopia_auth_token';
|
||||
const authChangeEvent = 'pokopia-auth-change';
|
||||
|
||||
@@ -16,9 +17,35 @@ export interface Language {
|
||||
}
|
||||
|
||||
export function setApiBaseUrl(value: unknown): void {
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
apiBaseUrl = value.trim();
|
||||
setApiBaseUrls({ browser: value, server: value });
|
||||
}
|
||||
|
||||
export function setApiBaseUrls(value: { browser?: unknown; server?: unknown }): void {
|
||||
const browserBaseUrl = normalizeApiBaseUrl(value.browser);
|
||||
const serverBaseUrl = normalizeApiBaseUrl(value.server);
|
||||
|
||||
if (browserBaseUrl) {
|
||||
browserApiBaseUrl = browserBaseUrl;
|
||||
}
|
||||
if (serverBaseUrl) {
|
||||
serverApiBaseUrl = serverBaseUrl;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeApiBaseUrl(value: unknown): string | null {
|
||||
if (typeof value === 'string' && value.trim() !== '') {
|
||||
return value.trim().replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function activeApiBaseUrl(): string {
|
||||
return typeof window === 'undefined' ? serverApiBaseUrl : browserApiBaseUrl;
|
||||
}
|
||||
|
||||
function apiUrl(path: string): string {
|
||||
return `${activeApiBaseUrl()}${path}`;
|
||||
}
|
||||
|
||||
export type SystemWordingSurface = 'frontend' | 'backend' | 'email';
|
||||
@@ -1086,7 +1113,7 @@ function requestHeaders(): HeadersInit {
|
||||
}
|
||||
|
||||
export function notificationWebSocketUrl(ticket: string): string {
|
||||
const base = new URL(apiBaseUrl, typeof window === 'undefined' ? 'http://localhost' : window.location.origin);
|
||||
const base = new URL(browserApiBaseUrl, typeof window === 'undefined' ? 'http://localhost' : window.location.origin);
|
||||
base.protocol = base.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
base.pathname = '/api/notifications/ws';
|
||||
base.search = '';
|
||||
@@ -1108,7 +1135,7 @@ async function getErrorMessage(response: Response): Promise<string> {
|
||||
}
|
||||
|
||||
async function getJson<T>(path: string, signal?: AbortSignal): Promise<T> {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||
const response = await fetch(apiUrl(path), {
|
||||
headers: requestHeaders(),
|
||||
signal
|
||||
});
|
||||
@@ -1121,7 +1148,7 @@ async function getJson<T>(path: string, signal?: AbortSignal): Promise<T> {
|
||||
}
|
||||
|
||||
async function sendJson<T>(path: string, method: 'PATCH' | 'POST' | 'PUT', body: unknown): Promise<T> {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||
const response = await fetch(apiUrl(path), {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -1138,7 +1165,7 @@ async function sendJson<T>(path: string, method: 'PATCH' | 'POST' | 'PUT', body:
|
||||
}
|
||||
|
||||
async function sendFormData<T>(path: string, body: FormData): Promise<T> {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||
const response = await fetch(apiUrl(path), {
|
||||
method: 'POST',
|
||||
headers: requestHeaders(),
|
||||
body
|
||||
@@ -1152,7 +1179,7 @@ async function sendFormData<T>(path: string, body: FormData): Promise<T> {
|
||||
}
|
||||
|
||||
async function postEmpty(path: string): Promise<void> {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||
const response = await fetch(apiUrl(path), {
|
||||
method: 'POST',
|
||||
headers: requestHeaders()
|
||||
});
|
||||
@@ -1163,7 +1190,7 @@ async function postEmpty(path: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function deleteJson(path: string): Promise<void> {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||
const response = await fetch(apiUrl(path), {
|
||||
method: 'DELETE',
|
||||
headers: requestHeaders()
|
||||
});
|
||||
@@ -1174,7 +1201,7 @@ async function deleteJson(path: string): Promise<void> {
|
||||
}
|
||||
|
||||
async function deleteAndGetJson<T>(path: string): Promise<T> {
|
||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
||||
const response = await fetch(apiUrl(path), {
|
||||
method: 'DELETE',
|
||||
headers: requestHeaders()
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user