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
|
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/*
|
||||||
|
|||||||
26
Dockerfile
26
Dockerfile
@@ -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;"]
|
||||||
|
|||||||
22
nginx.conf
22
nginx.conf
@@ -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";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 { 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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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;
|
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"),
|
||||||
|
|||||||
Reference in New Issue
Block a user