Revert the self-hosted deploy fix

Restores the tree to 3b16d50, reverting:

  3c649fc  Fix blank page on self-hosted deploys

The earlier public-route commits were already reverted by 0ddbd35 and a51ab4a,
so this returns the working tree exactly to the state at 3b16d50.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 21:01:49 +05:30
parent a51ab4ade8
commit 9a7852c8a2
8 changed files with 15 additions and 155 deletions

View File

@@ -9,14 +9,10 @@ lerna-debug.log*
node_modules node_modules
# The local dist/ is a developer's last build, not part of the source. Shipping it # dist
# into the build context only slows the upload and risks a stale artifact being
# picked up if `npm run build` ever fails to overwrite a file.
dist
!sdk/dist !sdk/dist
dist-ssr dist-ssr
*.local *.local
.git
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

View File

@@ -9,31 +9,13 @@ WORKDIR /app
# the server. Raise this only if the host gets more RAM. # the server. Raise this only if the host gets more RAM.
ENV NODE_OPTIONS=--max-old-space-size=1024 ENV NODE_OPTIONS=--max-old-space-size=1024
# Vite inlines VITE_* vars into the bundle at build time, so these have to be
# present here — setting them as runtime env vars in Dokploy does nothing, the
# JS is already compiled by then. .env.* is gitignored, so a build from the repo
# has no other source for them: without these the app boots with a null app id,
# every API call resolves against our own origin, nginx answers with index.html
# and the browser shows a blank page. Defaults match this app so a plain
# `docker build` works; override via Dokploy's build args to point elsewhere.
ARG VITE_BASE44_APP_ID=68fc6cf01386035c266e7a5d
ARG VITE_BASE44_BACKEND_URL=https://base44.app
ENV VITE_BASE44_APP_ID=$VITE_BASE44_APP_ID
ENV VITE_BASE44_BACKEND_URL=$VITE_BASE44_BACKEND_URL
# Install deps from the lockfile first so this layer is reused when only src changes # Install deps from the lockfile first so this layer is reused when only src changes
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci --no-audit --no-fund RUN npm ci --no-audit --no-fund
COPY . . COPY . .
# Fail loudly here rather than shipping an image that serves an empty page.
RUN test -n "$VITE_BASE44_APP_ID" || (echo "VITE_BASE44_APP_ID is empty — the build would produce a blank app" && exit 1)
RUN npm run build RUN npm run build
# The build is only useful if it actually emitted an entry point.
RUN test -f dist/index.html || (echo "vite build produced no dist/index.html" && exit 1)
# ---- Runtime stage ---- # ---- Runtime stage ----
FROM nginx:alpine FROM nginx:alpine
@@ -45,14 +27,6 @@ COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf COPY nginx.conf /etc/nginx/nginx.conf
# Catch a broken config at build time instead of at container start, where
# Dokploy would report the deploy as successful and then serve nothing.
RUN nginx -t
EXPOSE 80 3000 EXPOSE 80 3000
# Gives Dokploy a real signal that the container is serving, not just running.
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q --spider http://127.0.0.1/health || exit 1
CMD ["nginx", "-g", "daemon off;"] CMD ["nginx", "-g", "daemon off;"]

View File

@@ -49,29 +49,12 @@ http {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
# Container liveness for Dokploy/Docker. Kept above the SPA fallback so it
# answers with a real status instead of 200-plus-index.html, which would
# make any health check pass even with the app completely broken.
location = /health {
access_log off;
add_header Content-Type text/plain;
return 200 "ok\n";
}
# Vite writes content-hashed filenames here, so they can be cached forever. # Vite writes content-hashed filenames here, so they can be cached forever.
# Only add_header is used — combining it with `expires` emits two # Only add_header is used — combining it with `expires` emits two
# conflicting Cache-Control headers. # conflicting Cache-Control headers.
#
# try_files ending in =404 is deliberate: without it a stale index.html
# asking for an asset from a previous deploy would fall through to the
# SPA rule below and receive index.html with a 200 and a text/html type.
# The browser refuses to execute that as a module and the page renders
# empty with only a MIME error in the console — a real 404 is far easier
# to diagnose and lets a reload recover.
location /assets/ { location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable"; add_header Cache-Control "public, max-age=31536000, immutable";
access_log off; access_log off;
try_files $uri =404;
} }
# index.html must be revalidated every time or deploys won't take effect # index.html must be revalidated every time or deploys won't take effect
@@ -82,11 +65,6 @@ http {
location / { location / {
# This line forces Nginx to pass routing back to React Router # This line forces Nginx to pass routing back to React Router
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
# The fallback is served under the requested path, so the `= /index.html`
# rule above never applies to it. Without this a proxy or browser can
# cache the old HTML and keep loading assets that no longer exist.
add_header Cache-Control "no-cache";
} }
} }
} }

View File

@@ -9,8 +9,6 @@ import { BrowserRouter as Router, Route, Routes, useLocation } from 'react-route
import { setupIframeMessaging } from './lib/iframe-messaging'; import { setupIframeMessaging } from './lib/iframe-messaging';
import PageNotFound from './lib/PageNotFound'; import PageNotFound from './lib/PageNotFound';
import { AuthProvider, useAuth } from '@/lib/AuthContext'; import { AuthProvider, useAuth } from '@/lib/AuthContext';
import ErrorBoundary from '@/lib/ErrorBoundary';
import { appParams } from '@/lib/app-params';
import UserNotRegisteredError from '@/components/UserNotRegisteredError'; import UserNotRegisteredError from '@/components/UserNotRegisteredError';
import FeatureReport from './pages/FeatureReport'; import FeatureReport from './pages/FeatureReport';
import LandingPage from './pages/LandingPage'; import LandingPage from './pages/LandingPage';
@@ -72,22 +70,6 @@ const AuthenticatedApp = () => {
navigateToLogin(); navigateToLogin();
return null; return null;
} }
// Anything else (bad/missing app id, unreachable backend, network failure)
// used to fall straight through to the app, which then crashed on the empty
// data and left a blank page. Say what happened instead.
return (
<div className="min-h-screen flex items-center justify-center bg-slate-50 p-6">
<div className="max-w-lg w-full bg-white border border-slate-200 rounded-lg shadow-sm p-6">
<h1 className="text-lg font-semibold text-slate-900">Unable to load the app</h1>
<p className="mt-2 text-sm text-slate-600">{authError.message || 'The Base44 backend could not be reached.'}</p>
<p className="mt-4 text-xs text-slate-500">
App ID: <code>{String(appParams.appId)}</code><br />
Backend: <code>{String(appParams.serverUrl)}</code>
</p>
</div>
</div>
);
} }
// Render the main app // Render the main app
@@ -144,18 +126,16 @@ const AuthenticatedApp = () => {
function App() { function App() {
return ( return (
<ErrorBoundary> <AuthProvider>
<AuthProvider> <QueryClientProvider client={queryClientInstance}>
<QueryClientProvider client={queryClientInstance}> <Router>
<Router> <NavigationTracker />
<NavigationTracker /> <AuthenticatedApp />
<AuthenticatedApp /> </Router>
</Router> <Toaster />
<Toaster /> <VisualEditAgent />
<VisualEditAgent /> </QueryClientProvider>
</QueryClientProvider> </AuthProvider>
</AuthProvider>
</ErrorBoundary>
) )
} }

View File

@@ -3,15 +3,10 @@ import { appParams } from '@/lib/app-params';
const { appId, serverUrl, token, functionsVersion } = appParams; const { appId, serverUrl, token, functionsVersion } = appParams;
// appBaseUrl is what the SDK prefixes onto its login/logout redirects. Left unset //Create a client with authentication required
// it normalises to "" and those redirects become relative — on a self-hosted
// domain that sends the browser to /login on our own nginx, which serves
// index.html, which bootstraps the app, which redirects again: a loop that shows
// as a blank page. Pointing it at Base44 sends auth where it actually lives.
export const base44 = createClient({ export const base44 = createClient({
appId, appId,
serverUrl, serverUrl,
appBaseUrl: serverUrl,
token, token,
functionsVersion, functionsVersion,
requiresAuth: false requiresAuth: false

View File

@@ -124,11 +124,8 @@ export const AuthProvider = ({ children }) => {
}; };
const navigateToLogin = () => { const navigateToLogin = () => {
// redirectToLogin() posts to `${appBaseUrl}/login` with no app_id, so Base44 // Use the SDK's redirectToLogin method
// cannot tell which app is asking when the request comes from our own domain base44.auth.redirectToLogin(window.location.href);
// instead of a *.base44.app one. loginWithProvider() hits the same login flow
// but carries app_id in the query, which is what a self-hosted build needs.
base44.auth.loginWithProvider('google', window.location.href);
}; };
return ( return (

View File

@@ -1,52 +0,0 @@
import React from 'react';
/**
* Catches render-time crashes so they surface as a readable message instead of a
* white screen. React unmounts the whole tree when an error escapes, which is
* indistinguishable from "the deploy didn't work" — this makes the difference
* visible without opening devtools, which matters most on a hosted build where
* the stack is minified and nobody is watching the console.
*/
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, info) {
console.error('Application crashed:', error, info?.componentStack);
}
render() {
if (!this.state.error) {
return this.props.children;
}
return (
<div className="min-h-screen flex items-center justify-center bg-slate-50 p-6">
<div className="max-w-lg w-full bg-white border border-slate-200 rounded-lg shadow-sm p-6">
<h1 className="text-lg font-semibold text-slate-900">Something went wrong</h1>
<p className="mt-2 text-sm text-slate-600">
The application failed to render. The details below are also in the browser console.
</p>
<pre className="mt-4 p-3 bg-slate-100 rounded text-xs text-slate-800 overflow-auto max-h-64 whitespace-pre-wrap">
{String(this.state.error?.stack || this.state.error)}
</pre>
<button
type="button"
onClick={() => window.location.reload()}
className="mt-4 px-4 py-2 text-sm font-medium text-white bg-slate-900 rounded hover:bg-slate-700"
>
Reload
</button>
</div>
</div>
);
}
}
export default ErrorBoundary;

View File

@@ -34,18 +34,10 @@ const getAppParamValue = (paramName, { defaultValue = undefined, removeFromUrl =
return null; return null;
} }
// Base44's own host. On a base44-hosted app these arrive as ?app_id=/?server_url=
// query params, but a self-hosted build (Docker/Dokploy on a custom domain) never
// gets them, so the build-time env vars below are the only source. If both are
// missing the SDK falls back to a null serverUrl, every request becomes a
// same-origin relative URL, nginx answers it with index.html, and the app renders
// a blank page — so serverUrl must never be allowed to end up null.
const DEFAULT_SERVER_URL = "https://base44.app";
const getAppParams = () => { const getAppParams = () => {
return { return {
appId: getAppParamValue("app_id", { defaultValue: import.meta.env.VITE_BASE44_APP_ID }), appId: getAppParamValue("app_id", { defaultValue: import.meta.env.VITE_BASE44_APP_ID }),
serverUrl: getAppParamValue("server_url", { defaultValue: import.meta.env.VITE_BASE44_BACKEND_URL }) || DEFAULT_SERVER_URL, serverUrl: getAppParamValue("server_url", { defaultValue: import.meta.env.VITE_BASE44_BACKEND_URL }),
token: getAppParamValue("access_token", { removeFromUrl: true }), token: getAppParamValue("access_token", { removeFromUrl: true }),
fromUrl: getAppParamValue("from_url", { defaultValue: window.location.href }), fromUrl: getAppParamValue("from_url", { defaultValue: window.location.href }),
functionsVersion: getAppParamValue("functions_version"), functionsVersion: getAppParamValue("functions_version"),