feat(remix): support serving app under a sub-path via NEXT_PUBLIC_BASE_PATH (#2824)

This commit is contained in:
Christopher Ryan
2026-08-12 16:12:28 +10:00
committed by GitHub
parent 962cffc9f5
commit 1bd09480e6
27 changed files with 189 additions and 50 deletions
+10 -7
View File
@@ -1,5 +1,6 @@
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
import { SessionProvider } from '@documenso/lib/client-only/providers/session';
import { getBasePath } from '@documenso/lib/constants/app';
import { APP_I18N_OPTIONS, type SupportedLanguageCodes } from '@documenso/lib/constants/i18n';
import { createPublicEnv } from '@documenso/lib/utils/env';
import { extractLocaleData } from '@documenso/lib/utils/i18n';
@@ -20,7 +21,6 @@ import {
useMatches,
} from 'react-router';
import { PreventFlashOnWrongTheme, ThemeProvider, useTheme } from 'remix-themes';
import type { Route } from './+types/root';
import stylesheet from './app.css?url';
import { GenericErrorLayout } from './components/general/generic-error-layout';
@@ -68,6 +68,7 @@ export async function loader({ context, request }: Route.LoaderArgs) {
lang,
theme: getTheme(),
disableAnimations,
basePath: getBasePath(),
// Surface the per-request CSP nonce produced by `securityHeadersMiddleware` so all
// SSR-rendered <script>/<style> elements in this layout (and child
// routes that need it) can carry the matching nonce attribute.
@@ -90,10 +91,10 @@ export async function loader({ context, request }: Route.LoaderArgs) {
}
export function Layout({ children }: { children: React.ReactNode }) {
const { theme } = useLoaderData<typeof loader>() || {};
const { theme, basePath } = useLoaderData<typeof loader>() || {};
return (
<ThemeProvider specifiedTheme={theme} themeAction="/api/theme">
<ThemeProvider specifiedTheme={theme} themeAction={`${basePath ?? ''}/api/theme`}>
<LayoutContent>{children}</LayoutContent>
</ThemeProvider>
);
@@ -111,6 +112,8 @@ export function LayoutContent({ children }: { children: React.ReactNode }) {
const [theme] = useTheme();
const basePath = data.basePath ?? '';
// Recipient routes (signing pages) put `documenso-branded` on <body> so the
// <style> block from `RecipientBranding` applies to BOTH the main tree and
// any portaled content (Radix dialogs/popovers/dropdowns mount outside the
@@ -126,11 +129,11 @@ export function LayoutContent({ children }: { children: React.ReactNode }) {
<html translate="no" lang={lang} data-theme={theme} className={theme ?? ''} suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href={`${basePath}/apple-touch-icon.png`} />
<link rel="icon" type="image/png" sizes="32x32" href={`${basePath}/favicon-32x32.png`} />
<link rel="icon" type="image/png" sizes="16x16" href={`${basePath}/favicon-16x16.png`} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="manifest" href={`${basePath}/site.webmanifest`} />
<meta name="google" content="notranslate" />
<Meta />
<Links nonce={nonce(cspNonce)} />