fix(frontend): resolve API base URL at request time for production
Use same-origin /api in the browser so builds are not stuck with baked-in localhost. Server-side fetches use INTERNAL_API_URL, NEXT_PUBLIC_API_URL, or loopback. Centralize logic in lib/api-base.ts. Made-with: Cursor
This commit is contained in:
@@ -1,10 +1,9 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { apiUrl } from '@/lib/api-base';
|
||||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000/api';
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
const name = req.nextUrl.searchParams.get('name');
|
const name = req.nextUrl.searchParams.get('name');
|
||||||
const upstream = new URL(`${API_URL}/nip05`);
|
const upstream = new URL(apiUrl('/nip05'));
|
||||||
if (name) upstream.searchParams.set('name', name);
|
if (name) upstream.searchParams.set('name', name);
|
||||||
|
|
||||||
const res = await fetch(upstream.toString(), { cache: 'no-store' });
|
const res = await fetch(upstream.toString(), { cache: 'no-store' });
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import BlogPostClient from "./BlogPostClient";
|
import BlogPostClient from "./BlogPostClient";
|
||||||
import { BlogPostingJsonLd, BreadcrumbJsonLd } from "@/components/public/JsonLd";
|
import { BlogPostingJsonLd, BreadcrumbJsonLd } from "@/components/public/JsonLd";
|
||||||
|
import { apiUrl } from "@/lib/api-base";
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api";
|
|
||||||
|
|
||||||
async function fetchPost(slug: string) {
|
async function fetchPost(slug: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${apiUrl}/posts/${slug}`, {
|
const res = await fetch(apiUrl(`/posts/${slug}`), {
|
||||||
next: { revalidate: 300 },
|
next: { revalidate: 300 },
|
||||||
});
|
});
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
import { apiUrl } from '@/lib/api-base';
|
||||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000/api';
|
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
let upstream: Response;
|
let upstream: Response;
|
||||||
try {
|
try {
|
||||||
upstream = await fetch(`${API_URL}/calendar/ics`, {
|
upstream = await fetch(apiUrl('/calendar/ics'), {
|
||||||
headers: { Accept: 'text/calendar' },
|
headers: { Accept: 'text/calendar' },
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import EventDetailClient from "./EventDetailClient";
|
import EventDetailClient from "./EventDetailClient";
|
||||||
import { EventJsonLd, BreadcrumbJsonLd } from "@/components/public/JsonLd";
|
import { EventJsonLd, BreadcrumbJsonLd } from "@/components/public/JsonLd";
|
||||||
|
import { apiUrl } from "@/lib/api-base";
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api";
|
|
||||||
|
|
||||||
async function fetchEvent(id: string) {
|
async function fetchEvent(id: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${apiUrl}/meetups/${id}`, {
|
const res = await fetch(apiUrl(`/meetups/${id}`), {
|
||||||
next: { revalidate: 300 },
|
next: { revalidate: 300 },
|
||||||
});
|
});
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
import { apiUrl } from "@/lib/api-base";
|
||||||
|
|
||||||
const siteUrl =
|
const siteUrl =
|
||||||
process.env.NEXT_PUBLIC_SITE_URL || "https://belgianbitcoinembassy.org";
|
process.env.NEXT_PUBLIC_SITE_URL || "https://belgianbitcoinembassy.org";
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api";
|
|
||||||
|
|
||||||
async function fetchJson<T>(path: string): Promise<T | null> {
|
async function fetchJson<T>(path: string): Promise<T | null> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${apiUrl}${path}`, { next: { revalidate: 3600 } });
|
const res = await fetch(apiUrl(path), { next: { revalidate: 3600 } });
|
||||||
if (!res.ok) return null;
|
if (!res.ok) return null;
|
||||||
return res.json();
|
return res.json();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
12
frontend/lib/api-base.ts
Normal file
12
frontend/lib/api-base.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/** Browser: `/api/...` (same origin). Server: INTERNAL_API_URL → NEXT_PUBLIC_API_URL → loopback. */
|
||||||
|
export function apiUrl(path: string): string {
|
||||||
|
const p = path.startsWith("/") ? path : `/${path}`;
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
return `/api${p}`;
|
||||||
|
}
|
||||||
|
const base =
|
||||||
|
process.env.INTERNAL_API_URL ||
|
||||||
|
process.env.NEXT_PUBLIC_API_URL ||
|
||||||
|
"http://127.0.0.1:4000/api";
|
||||||
|
return `${base}${p}`;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api";
|
import { apiUrl } from "./api-base";
|
||||||
|
|
||||||
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
||||||
const token = typeof window !== "undefined" ? localStorage.getItem("bbe_token") : null;
|
const token = typeof window !== "undefined" ? localStorage.getItem("bbe_token") : null;
|
||||||
@@ -8,7 +8,7 @@ async function request<T>(path: string, options?: RequestInit): Promise<T> {
|
|||||||
...options?.headers,
|
...options?.headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(`${API_URL}${path}`, { ...options, headers });
|
const res = await fetch(apiUrl(path), { ...options, headers });
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const error = await res.json().catch(() => ({ message: "Request failed" }));
|
const error = await res.json().catch(() => ({ message: "Request failed" }));
|
||||||
throw new Error(error.message || `HTTP ${res.status}`);
|
throw new Error(error.message || `HTTP ${res.status}`);
|
||||||
@@ -128,7 +128,7 @@ export const api = {
|
|||||||
const token = typeof window !== "undefined" ? localStorage.getItem("bbe_token") : null;
|
const token = typeof window !== "undefined" ? localStorage.getItem("bbe_token") : null;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
const res = await fetch(`${API_URL}/media/upload`, {
|
const res = await fetch(apiUrl("/media/upload"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||||
body: formData,
|
body: formData,
|
||||||
|
|||||||
Reference in New Issue
Block a user