Revert the self-hosted deploy fix
Restores the tree to3b16d50, reverting:3c649fcFix blank page on self-hosted deploys The earlier public-route commits were already reverted by0ddbd35anda51ab4a, so this returns the working tree exactly to the state at3b16d50. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,14 +9,10 @@ lerna-debug.log*
|
||||
|
||||
|
||||
node_modules
|
||||
# The local dist/ is a developer's last build, not part of the source. Shipping it
|
||||
# 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
|
||||
# dist
|
||||
!sdk/dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.git
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
|
||||
26
Dockerfile
26
Dockerfile
@@ -9,31 +9,13 @@ WORKDIR /app
|
||||
# the server. Raise this only if the host gets more RAM.
|
||||
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
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --no-audit --no-fund
|
||||
|
||||
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
|
||||
|
||||
# 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 ----
|
||||
FROM nginx:alpine
|
||||
|
||||
@@ -45,14 +27,6 @@ COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
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
|
||||
|
||||
# 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;"]
|
||||
|
||||
22
nginx.conf
22
nginx.conf
@@ -49,29 +49,12 @@ http {
|
||||
root /usr/share/nginx/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.
|
||||
# Only add_header is used — combining it with `expires` emits two
|
||||
# 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/ {
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
access_log off;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# index.html must be revalidated every time or deploys won't take effect
|
||||
@@ -82,11 +65,6 @@ http {
|
||||
location / {
|
||||
# This line forces Nginx to pass routing back to React Router
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
src/App.jsx
40
src/App.jsx
@@ -9,8 +9,6 @@ import { BrowserRouter as Router, Route, Routes, useLocation } from 'react-route
|
||||
import { setupIframeMessaging } from './lib/iframe-messaging';
|
||||
import PageNotFound from './lib/PageNotFound';
|
||||
import { AuthProvider, useAuth } from '@/lib/AuthContext';
|
||||
import ErrorBoundary from '@/lib/ErrorBoundary';
|
||||
import { appParams } from '@/lib/app-params';
|
||||
import UserNotRegisteredError from '@/components/UserNotRegisteredError';
|
||||
import FeatureReport from './pages/FeatureReport';
|
||||
import LandingPage from './pages/LandingPage';
|
||||
@@ -72,22 +70,6 @@ const AuthenticatedApp = () => {
|
||||
navigateToLogin();
|
||||
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
|
||||
@@ -144,18 +126,16 @@ const AuthenticatedApp = () => {
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<AuthProvider>
|
||||
<QueryClientProvider client={queryClientInstance}>
|
||||
<Router>
|
||||
<NavigationTracker />
|
||||
<AuthenticatedApp />
|
||||
</Router>
|
||||
<Toaster />
|
||||
<VisualEditAgent />
|
||||
</QueryClientProvider>
|
||||
</AuthProvider>
|
||||
</ErrorBoundary>
|
||||
<AuthProvider>
|
||||
<QueryClientProvider client={queryClientInstance}>
|
||||
<Router>
|
||||
<NavigationTracker />
|
||||
<AuthenticatedApp />
|
||||
</Router>
|
||||
<Toaster />
|
||||
<VisualEditAgent />
|
||||
</QueryClientProvider>
|
||||
</AuthProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,10 @@ import { appParams } from '@/lib/app-params';
|
||||
|
||||
const { appId, serverUrl, token, functionsVersion } = appParams;
|
||||
|
||||
// appBaseUrl is what the SDK prefixes onto its login/logout redirects. Left unset
|
||||
// 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.
|
||||
//Create a client with authentication required
|
||||
export const base44 = createClient({
|
||||
appId,
|
||||
serverUrl,
|
||||
appBaseUrl: serverUrl,
|
||||
token,
|
||||
functionsVersion,
|
||||
requiresAuth: false
|
||||
|
||||
@@ -124,11 +124,8 @@ export const AuthProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
const navigateToLogin = () => {
|
||||
// redirectToLogin() posts to `${appBaseUrl}/login` with no app_id, so Base44
|
||||
// cannot tell which app is asking when the request comes from our own domain
|
||||
// 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);
|
||||
// Use the SDK's redirectToLogin method
|
||||
base44.auth.redirectToLogin(window.location.href);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -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;
|
||||
@@ -34,18 +34,10 @@ const getAppParamValue = (paramName, { defaultValue = undefined, removeFromUrl =
|
||||
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 = () => {
|
||||
return {
|
||||
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 }),
|
||||
fromUrl: getAppParamValue("from_url", { defaultValue: window.location.href }),
|
||||
functionsVersion: getAppParamValue("functions_version"),
|
||||
|
||||
Reference in New Issue
Block a user