From 88ea74a46e87b305d500769c362269290d8e6077 Mon Sep 17 00:00:00 2001 From: Juned Memon Date: Thu, 11 Sep 2025 18:22:24 +0530 Subject: [PATCH 1/2] feat: Added BASE_PATH so that application can run on a relative path like /bolt --- .../gitlab/components/GitLabRepositorySelector.tsx | 5 ++++- .../@settings/tabs/supabase/SupabaseTab.tsx | 5 ++++- app/components/chat/APIKeyManager.tsx | 7 ++++--- app/components/chat/BaseChat.tsx | 10 ++++++++-- app/components/chat/Chat.client.tsx | 4 +++- app/components/chat/SupabaseAlert.tsx | 5 ++++- app/components/deploy/NetlifyDeploy.client.tsx | 5 ++++- app/components/deploy/VercelDeploy.client.tsx | 5 ++++- app/components/header/Header.tsx | 11 ++++++++--- app/components/ui/BranchSelector.tsx | 7 +++++-- app/lib/api/connection.ts | 7 ++++--- app/lib/hooks/useGitHubConnection.ts | 2 +- app/lib/hooks/usePromptEnhancer.ts | 5 ++++- app/lib/hooks/useSupabaseConnection.ts | 5 ++++- app/lib/stores/github.ts | 2 +- app/lib/stores/mcp.ts | 7 +++++-- app/lib/stores/supabase.ts | 4 ++-- app/lib/webcontainer/index.ts | 6 +++++- app/root.tsx | 7 ++++++- app/utils/debugLogger.ts | 2 +- app/utils/selectStarterTemplate.ts | 11 ++++++++--- vite.config.ts | 4 +++- 22 files changed, 92 insertions(+), 34 deletions(-) diff --git a/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx b/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx index 3f56bb13dc..207d01c3cb 100644 --- a/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx +++ b/app/components/@settings/tabs/gitlab/components/GitLabRepositorySelector.tsx @@ -42,7 +42,10 @@ export function GitLabRepositorySelector({ onClone, className }: GitLabRepositor setError(null); try { - const response = await fetch('/api/gitlab-projects', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/gitlab-projects`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/components/@settings/tabs/supabase/SupabaseTab.tsx b/app/components/@settings/tabs/supabase/SupabaseTab.tsx index cec555a683..8f4a1271dd 100644 --- a/app/components/@settings/tabs/supabase/SupabaseTab.tsx +++ b/app/components/@settings/tabs/supabase/SupabaseTab.tsx @@ -70,7 +70,10 @@ export default function SupabaseTab() { }); try { - const response = await fetch('/api/supabase-user', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/supabase-user`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/app/components/chat/APIKeyManager.tsx b/app/components/chat/APIKeyManager.tsx index 92263363fa..c8d9263732 100644 --- a/app/components/chat/APIKeyManager.tsx +++ b/app/components/chat/APIKeyManager.tsx @@ -56,7 +56,8 @@ export const APIKeyManager: React.FC = ({ provider, apiKey, } try { - const response = await fetch(`/api/check-env-key?provider=${encodeURIComponent(provider.name)}`); + const basePath = import.meta.env.VITE_BASE_PATH || '/'; + const response = await fetch(`${basePath}/api/check-env-key?provider=${encodeURIComponent(provider.name)}`); const data = await response.json(); const isSet = (data as { isSet: boolean }).isSet; @@ -121,8 +122,8 @@ export const APIKeyManager: React.FC = ({ provider, apiKey, value={tempKey} placeholder="Enter API Key" onChange={(e) => setTempKey(e.target.value)} - className="w-[300px] px-3 py-1.5 text-sm rounded border border-bolt-elements-borderColor - bg-bolt-elements-prompt-background text-bolt-elements-textPrimary + className="w-[300px] px-3 py-1.5 text-sm rounded border border-bolt-elements-borderColor + bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus" /> ( } setIsModelLoading('all'); - fetch('/api/models') + + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/models`.replace(/\/+/g, '/'); + fetch(apiUrl) .then((response) => response.json()) .then((data) => { const typedData = data as { modelList: ModelInfo[] }; @@ -237,7 +241,9 @@ export const BaseChat = React.forwardRef( let providerModels: ModelInfo[] = []; try { - const response = await fetch(`/api/models/${encodeURIComponent(providerName)}`); + const response = await fetch( + `${import.meta.env.VITE_BASE_PATH || ''}/api/models/${encodeURIComponent(providerName)}`, + ); const data = await response.json(); providerModels = (data as { modelList: ModelInfo[] }).modelList; } catch (error) { diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx index dfe891220d..97e76b2688 100644 --- a/app/components/chat/Chat.client.tsx +++ b/app/components/chat/Chat.client.tsx @@ -150,6 +150,8 @@ export const ChatImpl = memo( const [selectedElement, setSelectedElement] = useState(null); const mcpSettings = useMCPStore((state) => state.settings); + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; const { messages, isLoading, @@ -165,7 +167,7 @@ export const ChatImpl = memo( setData, addToolResult, } = useChat({ - api: '/api/chat', + api: `${basePath}/api/chat`.replace(/\/+/g, '/'), body: { apiKeys, files, diff --git a/app/components/chat/SupabaseAlert.tsx b/app/components/chat/SupabaseAlert.tsx index 414a6e5159..4d9a9b3ef8 100644 --- a/app/components/chat/SupabaseAlert.tsx +++ b/app/components/chat/SupabaseAlert.tsx @@ -44,7 +44,10 @@ export function SupabaseChatAlert({ alert, clearAlert, postMessage }: Props) { setIsExecuting(true); try { - const response = await fetch('/api/supabase/query', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/supabase/query`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/components/deploy/NetlifyDeploy.client.tsx b/app/components/deploy/NetlifyDeploy.client.tsx index 62b95f44de..54fbcce662 100644 --- a/app/components/deploy/NetlifyDeploy.client.tsx +++ b/app/components/deploy/NetlifyDeploy.client.tsx @@ -139,7 +139,10 @@ export function useNetlifyDeploy() { // Use chatId instead of artifact.id const existingSiteId = localStorage.getItem(`netlify-site-${currentChatId}`); - const response = await fetch('/api/netlify-deploy', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/netlify-deploy`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/components/deploy/VercelDeploy.client.tsx b/app/components/deploy/VercelDeploy.client.tsx index d2303cde91..83f14c5a58 100644 --- a/app/components/deploy/VercelDeploy.client.tsx +++ b/app/components/deploy/VercelDeploy.client.tsx @@ -176,7 +176,10 @@ export function useVercelDeploy() { // Use chatId instead of artifact.id const existingProjectId = localStorage.getItem(`vercel-project-${currentChatId}`); - const response = await fetch('/api/vercel-deploy', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/vercel-deploy`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx index 1d509ce82e..ab99d8e2be 100644 --- a/app/components/header/Header.tsx +++ b/app/components/header/Header.tsx @@ -8,6 +8,11 @@ import { ChatDescription } from '~/lib/persistence/ChatDescription.client'; export function Header() { const chat = useStore(chatStore); + // Ensure asset URLs always start with a leading slash + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const assetUrl = (file: string) => `${basePath}/${file}`.replace(/\/+/g, '/'); + return (
{chat.started && ( // Display ChatDescription and HeaderActionButtons only when the chat has started. diff --git a/app/components/ui/BranchSelector.tsx b/app/components/ui/BranchSelector.tsx index 235eb6f57a..2f016f6906 100644 --- a/app/components/ui/BranchSelector.tsx +++ b/app/components/ui/BranchSelector.tsx @@ -55,7 +55,10 @@ export function BranchSelector({ let response: Response; if (provider === 'github') { - response = await fetch('/api/github-branches', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/github-branches`.replace(/\/+/g, '/'); + response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -70,7 +73,7 @@ export function BranchSelector({ throw new Error('Project ID is required for GitLab repositories'); } - response = await fetch('/api/gitlab-branches', { + response = await fetch(`${import.meta.env.VITE_BASE_PATH || ''}/api/gitlab-branches`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ diff --git a/app/lib/api/connection.ts b/app/lib/api/connection.ts index 92bd08f292..a4830537cc 100644 --- a/app/lib/api/connection.ts +++ b/app/lib/api/connection.ts @@ -18,10 +18,11 @@ export const checkConnection = async (): Promise => { } // Try multiple endpoints in case one fails + const BASE_PATH = (import.meta.env.VITE_BASE_PATH || '').replace(/\/$/, ''); const endpoints = [ - '/api/health', - '/', // Fallback to root route - '/favicon.ico', // Another common fallback + `${BASE_PATH}/api/health`.replace(/\/+/g, '/'), + `${BASE_PATH}/`, // Fallback to root route + `${BASE_PATH}/favicon.ico`, // Another common fallback ]; let latency = 0; diff --git a/app/lib/hooks/useGitHubConnection.ts b/app/lib/hooks/useGitHubConnection.ts index f88671ce9d..755682084e 100644 --- a/app/lib/hooks/useGitHubConnection.ts +++ b/app/lib/hooks/useGitHubConnection.ts @@ -215,7 +215,7 @@ export function useGitHubConnection(): UseGitHubConnectionReturn { const isServerSide = !connection.token; if (isServerSide) { - const response = await fetch('/api/github-user'); + const response = await fetch(`${import.meta.env.VITE_BASE_PATH || ''}/api/github-user`); return response.ok; } diff --git a/app/lib/hooks/usePromptEnhancer.ts b/app/lib/hooks/usePromptEnhancer.ts index 6275ef37eb..aebb1f4f91 100644 --- a/app/lib/hooks/usePromptEnhancer.ts +++ b/app/lib/hooks/usePromptEnhancer.ts @@ -33,7 +33,10 @@ export function usePromptEnhancer() { requestBody.apiKeys = apiKeys; } - const response = await fetch('/api/enhancer', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/enhancer`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', body: JSON.stringify(requestBody), }); diff --git a/app/lib/hooks/useSupabaseConnection.ts b/app/lib/hooks/useSupabaseConnection.ts index 918b41ea88..c9bca200a3 100644 --- a/app/lib/hooks/useSupabaseConnection.ts +++ b/app/lib/hooks/useSupabaseConnection.ts @@ -67,7 +67,10 @@ export function useSupabaseConnection() { try { const cleanToken = connection.token.trim(); - const response = await fetch('/api/supabase', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/supabase`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/lib/stores/github.ts b/app/lib/stores/github.ts index 19e15d596f..1941c4ee39 100644 --- a/app/lib/stores/github.ts +++ b/app/lib/stores/github.ts @@ -73,7 +73,7 @@ export async function fetchGitHubStatsViaAPI() { try { isFetchingStats.set(true); - const response = await fetch('/api/github-user', { + const response = await fetch(`${import.meta.env.VITE_BASE_PATH || ''}/api/github-user`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/lib/stores/mcp.ts b/app/lib/stores/mcp.ts index 18294ca5d3..b82f251633 100644 --- a/app/lib/stores/mcp.ts +++ b/app/lib/stores/mcp.ts @@ -84,7 +84,7 @@ export const useMCPStore = create((set, get) => ({ } }, checkServersAvailabilities: async () => { - const response = await fetch('/api/mcp-check', { + const response = await fetch(`${import.meta.env.VITE_BASE_PATH || ''}/api/mcp-check`, { method: 'GET', }); @@ -99,7 +99,10 @@ export const useMCPStore = create((set, get) => ({ })); async function updateServerConfig(config: MCPConfig) { - const response = await fetch('/api/mcp-update-config', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/mcp-update-config`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config), diff --git a/app/lib/stores/supabase.ts b/app/lib/stores/supabase.ts index fbee139016..5d6c8ec21a 100644 --- a/app/lib/stores/supabase.ts +++ b/app/lib/stores/supabase.ts @@ -151,7 +151,7 @@ export async function fetchSupabaseStats(token: string) { try { // Use the internal API route instead of direct Supabase API call - const response = await fetch('/api/supabase', { + const response = await fetch(`${import.meta.env.VITE_BASE_PATH || ''}/api/supabase`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -183,7 +183,7 @@ export async function fetchProjectApiKeys(projectId: string, token: string) { isFetchingApiKeys.set(true); try { - const response = await fetch('/api/supabase/variables', { + const response = await fetch(`${import.meta.env.VITE_BASE_PATH || ''}/api/supabase/variables`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/lib/webcontainer/index.ts b/app/lib/webcontainer/index.ts index 0de9401e8c..f695d493c3 100644 --- a/app/lib/webcontainer/index.ts +++ b/app/lib/webcontainer/index.ts @@ -34,7 +34,11 @@ if (!import.meta.env.SSR) { const { workbenchStore } = await import('~/lib/stores/workbench'); - const response = await fetch('/inspector-script.js'); + // Ensure asset URLs always start with a leading slash + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const assetUrl = (file: string) => `${basePath}/${file}`.replace(/\/+/g, '/'); + const response = await fetch(assetUrl('inspector-script.js')); const inspectorScript = await response.text(); await webcontainer.setPreviewScript(inspectorScript); diff --git a/app/root.tsx b/app/root.tsx index 9b8c692852..fb806f75d9 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -19,7 +19,12 @@ import 'virtual:uno.css'; export const links: LinksFunction = () => [ { rel: 'icon', - href: '/favicon.svg', + href: (() => { + const envBasePath = typeof window !== 'undefined' ? import.meta.env.VITE_BASE_PATH : process.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.endsWith('/') ? envBasePath : envBasePath + '/') : '/'; + + return `${basePath}favicon.svg`; + })(), type: 'image/svg+xml', }, { rel: 'stylesheet', href: reactToastifyStyles }, diff --git a/app/utils/debugLogger.ts b/app/utils/debugLogger.ts index b47829121d..08dc04d01b 100644 --- a/app/utils/debugLogger.ts +++ b/app/utils/debugLogger.ts @@ -906,7 +906,7 @@ class DebugLogger { private async _getGitInfo(): Promise { try { // Try to fetch git info from existing API endpoint - const response = await fetch('/api/system/git-info'); + const response = await fetch(`${import.meta.env.VITE_BASE_PATH || ''}/api/system/git-info`); if (response.ok) { const gitInfo = await response.json(); diff --git a/app/utils/selectStarterTemplate.ts b/app/utils/selectStarterTemplate.ts index e070aed0c4..6abada3b25 100644 --- a/app/utils/selectStarterTemplate.ts +++ b/app/utils/selectStarterTemplate.ts @@ -60,7 +60,7 @@ Instructions: 5. If no perfect match exists, recommend the closest option Important: Provide only the selection tags in your response, no additional text. -MOST IMPORTANT: YOU DONT HAVE TIME TO THINK JUST START RESPONDING BASED ON HUNCH +MOST IMPORTANT: YOU DONT HAVE TIME TO THINK JUST START RESPONDING BASED ON HUNCH `; const templates: Template[] = STARTER_TEMPLATES.filter((t) => !t.name.includes('shadcn')); @@ -90,7 +90,10 @@ export const selectStarterTemplate = async (options: { message: string; model: s provider, system: starterTemplateSelectionPrompt(templates), }; - const response = await fetch('/api/llmcall', { + const envBasePath = import.meta.env.VITE_BASE_PATH; + const basePath = envBasePath ? (envBasePath.startsWith('/') ? envBasePath : '/' + envBasePath) : ''; + const apiUrl = `${basePath}/api/llmcall`.replace(/\/+/g, '/'); + const response = await fetch(apiUrl, { method: 'POST', body: JSON.stringify(requestBody), }); @@ -115,7 +118,9 @@ export const selectStarterTemplate = async (options: { message: string; model: s const getGitHubRepoContent = async (repoName: string): Promise<{ name: string; path: string; content: string }[]> => { try { // Instead of directly fetching from GitHub, use our own API endpoint as a proxy - const response = await fetch(`/api/github-template?repo=${encodeURIComponent(repoName)}`); + const response = await fetch( + `${import.meta.env.VITE_BASE_PATH || ''}/api/github-template?repo=${encodeURIComponent(repoName)}`, + ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); diff --git a/vite.config.ts b/vite.config.ts index e0b096c8e8..e27724c1d4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,6 +13,7 @@ dotenv.config(); export default defineConfig((config) => { return { + base: process.env.VITE_BASE_PATH || '/', define: { 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), }, @@ -45,6 +46,7 @@ export default defineConfig((config) => { }, config.mode !== 'test' && remixCloudflareDevProxy(), remixVitePlugin({ + basename: process.env.VITE_BASE_PATH || '/', future: { v3_fetcherPersist: true, v3_relativeSplatPath: true, @@ -109,4 +111,4 @@ function chrome129IssuePlugin() { }); }, }; -} \ No newline at end of file +} From 24e50fc2e4f155662bbeb2e9103b2e0cccfdcfcb Mon Sep 17 00:00:00 2001 From: Juned Memon Date: Thu, 11 Sep 2025 18:25:02 +0530 Subject: [PATCH 2/2] feat: Added BASE_PATH so that application can run on a relative path like /bolt --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1d7ae4004d..d8e0bea27e 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,16 @@ You have two options for running Bolt.DIY: directly on your machine or using Doc ```bash pnpm run dev ``` - + + +To run the application with a custom base path (for example, `/bolt`), use: + +```bash +VITE_BASE_PATH=/bolt pnpm run dev +``` + +Replace `/bolt` with your desired base path if needed. This sets the frontend route base path. + ### Option 2: Using Docker This option requires some familiarity with Docker but provides a more isolated environment. @@ -378,7 +387,7 @@ This method is recommended for developers who want to: Hint: Be aware that this can have beta-features and more likely got bugs than the stable release >**Open the WebUI to test (Default: http://localhost:5173)** -> - Beginners: +> - Beginners: > - Try to use a sophisticated Provider/Model like Anthropic with Claude Sonnet 3.x Models to get best results > - Explanation: The System Prompt currently implemented in bolt.diy cant cover the best performance for all providers and models out there. So it works better with some models, then other, even if the models itself are perfect for >programming > - Future: Planned is a Plugin/Extentions-Library so there can be different System Prompts for different Models, which will help to get better results @@ -396,7 +405,7 @@ To get the latest changes from the repository: 2. **Pull Latest Updates**: ```bash - git pull + git pull ``` 3. **Update Dependencies**: