diff --git a/apps/web/app/(auth)/login/page.tsx b/apps/web/app/(auth)/login/page.tsx index c7723724..5f9bf8cc 100644 --- a/apps/web/app/(auth)/login/page.tsx +++ b/apps/web/app/(auth)/login/page.tsx @@ -18,7 +18,7 @@ import { motion } from "motion/react" import { dmSansClassName } from "@/lib/fonts" import { cn } from "@lib/utils" import { Logo } from "@ui/assets/Logo" -import { resolveAuthRedirectUrl } from "@/lib/url-helpers" +import { getBackendUrl, resolveAuthRedirectUrl } from "@/lib/url-helpers" import { Loader2 } from "lucide-react" function isMcpOAuthAuthorizeContext(sp: Pick): boolean { @@ -276,7 +276,7 @@ export default function LoginPage() { const token = formData.get("token") as string const callbackURL = getCallbackURL() router.push( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/auth/magic-link/verify?token=${token}&callbackURL=${encodeURIComponent(callbackURL)}`, + `${getBackendUrl()}/api/auth/magic-link/verify?token=${token}&callbackURL=${encodeURIComponent(callbackURL)}`, ) } diff --git a/apps/web/components/app-experience.tsx b/apps/web/components/app-experience.tsx index 63daad88..259584ee 100644 --- a/apps/web/components/app-experience.tsx +++ b/apps/web/components/app-experience.tsx @@ -67,6 +67,7 @@ import { } from "@/lib/search-params" import { getChatSpaceDisplayLabel } from "@/lib/chat-space-label" import { getToolDocumentSpace } from "@/lib/plugin-space" +import { getBackendUrl } from "@/lib/url-helpers" type DocumentsResponse = z.infer type DocumentWithMemories = DocumentsResponse["documents"][0] @@ -134,6 +135,7 @@ export function AppExperience() { const { viewMode, setViewMode } = useViewMode() useLegacyViewRedirect() const isCompanyBrain = useHasCompanyBrain() + const backendUrl = getBackendUrl() // Slack OAuth redirects back here with ?slack=connected — toast then clean up. useEffect(() => { @@ -331,7 +333,7 @@ export function AppExperience() { queryFn: async (): Promise => { const spaceId = selectedProject || "sm_project_default" const forceRefresh = highlightsForceAt > 0 - const cacheKey = `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/space-highlights?spaceId=${spaceId}` + const cacheKey = `${backendUrl}/v3/space-highlights?spaceId=${spaceId}` if (!forceRefresh) { const cache = await caches.open(HIGHLIGHTS_CACHE_NAME) @@ -345,22 +347,19 @@ export function AppExperience() { } } - const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/space-highlights`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - body: JSON.stringify({ - spaceId, - highlightsCount: 3, - questionsCount: 4, - includeHighlights: true, - includeQuestions: true, - forceRefresh, - }), - }, - ) + const response = await fetch(`${backendUrl}/v3/space-highlights`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ + spaceId, + highlightsCount: 3, + questionsCount: 4, + includeHighlights: true, + includeQuestions: true, + forceRefresh, + }), + }) if (!response.ok) { throw new Error("Failed to fetch space highlights") @@ -404,10 +403,9 @@ export function AppExperience() { if (stored) return JSON.parse(stored) as MemoryOfDay } catch {} - const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/memory-of-day`, - { credentials: "include" }, - ) + const response = await fetch(`${backendUrl}/v3/memory-of-day`, { + credentials: "include", + }) if (!response.ok) return null const data = (await response.json()) as MemoryOfDay | null if (data) { diff --git a/apps/web/components/chat/index.tsx b/apps/web/components/chat/index.tsx index aae5920b..c78df7f7 100644 --- a/apps/web/components/chat/index.tsx +++ b/apps/web/components/chat/index.tsx @@ -1111,7 +1111,7 @@ export function ChatSidebar({ const params = new URLSearchParams({ projectId: chatProject }) if (historyScope === "all") params.set("scope", "all") const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/threads?${params.toString()}`, + `${chatApiBase}/chat/threads?${params.toString()}`, { credentials: "include" }, ) if (response.ok) { @@ -1123,7 +1123,7 @@ export function ChatSidebar({ } finally { setIsLoadingThreads(false) } - }, [chatProject, historyScope]) + }, [chatApiBase, chatProject, historyScope]) useEffect(() => { if (!isHistoryOpen) return @@ -1134,10 +1134,9 @@ export function ChatSidebar({ const loadThread = useCallback( async (id: string) => { try { - const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/threads/${id}`, - { credentials: "include" }, - ) + const response = await fetch(`${chatApiBase}/chat/threads/${id}`, { + credentials: "include", + }) if (response.ok) { const data = await response.json() const uiMessages = data.messages.map( @@ -1179,7 +1178,7 @@ export function ChatSidebar({ console.error("Failed to load thread:", error) } }, - [setThreadId], + [chatApiBase, setThreadId], ) // Auto-restore thread from URL on mount (e.g. reload or direct link) @@ -1197,7 +1196,7 @@ export function ChatSidebar({ async (threadId: string) => { try { const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/chat/threads/${threadId}`, + `${chatApiBase}/chat/threads/${threadId}`, { method: "DELETE", credentials: "include" }, ) if (response.ok) { @@ -1213,7 +1212,7 @@ export function ChatSidebar({ setConfirmingDeleteId(null) } }, - [currentChatId, handleNewChat], + [chatApiBase, currentChatId, handleNewChat], ) const formatRelativeTime = (isoString: string): string => { diff --git a/apps/web/hooks/use-document-mutations.ts b/apps/web/hooks/use-document-mutations.ts index b9a08cc6..c0b1c864 100644 --- a/apps/web/hooks/use-document-mutations.ts +++ b/apps/web/hooks/use-document-mutations.ts @@ -10,6 +10,7 @@ import { $fetch } from "@lib/api" import { useAuth } from "@lib/auth-context" import { analytics } from "@/lib/analytics" import { fetchSpaceSettings, spaceSettingsKey } from "@/hooks/use-space-context" +import { getBackendUrl } from "@/lib/url-helpers" /** Pull the human-readable message out of a $fetch error (handles `{error}`/`{message}`/string). */ function fetchErrorMessage(err: unknown, fallback: string): string { @@ -500,14 +501,11 @@ export function useDocumentMutations({ } formData.append("metadata", JSON.stringify({ sm_source: "consumer" })) - const response = await fetch( - `${process.env.NEXT_PUBLIC_BACKEND_URL}/v3/documents/file`, - { - method: "POST", - body: formData, - credentials: "include", - }, - ) + const response = await fetch(`${getBackendUrl()}/v3/documents/file`, { + method: "POST", + body: formData, + credentials: "include", + }) if (!response.ok) { let message = "Failed to upload file" diff --git a/apps/web/lib/url-helpers.ts b/apps/web/lib/url-helpers.ts index 84e67b3e..e9f1dbc8 100644 --- a/apps/web/lib/url-helpers.ts +++ b/apps/web/lib/url-helpers.ts @@ -1,7 +1,15 @@ const PROXY_LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]) +const DEFAULT_BACKEND_URL = "https://api.supermemory.ai" const DEV_APP_ORIGIN = "https://app.dev.supermemory.ai" const PROD_APP_ORIGIN = "https://app.supermemory.ai" +export function getBackendUrl(): string { + return (process.env.NEXT_PUBLIC_BACKEND_URL ?? DEFAULT_BACKEND_URL).replace( + /\/+$/, + "", + ) +} + export function getAppOriginForCurrentEnvironment(hostname?: string): string { const currentHostname = hostname ?? (typeof window !== "undefined" ? window.location.hostname : "")