From 1bd09480e673546279cd31cb546abc0742abd807 Mon Sep 17 00:00:00 2001 From: Christopher Ryan <37274436+GMCfourX4@users.noreply.github.com> Date: Wed, 12 Aug 2026 02:12:28 -0400 Subject: [PATCH] feat(remix): support serving app under a sub-path via NEXT_PUBLIC_BASE_PATH (#2824) --- apps/remix/app/components/forms/signin.tsx | 3 +- .../components/general/app-command-menu.tsx | 3 +- .../document-signing-auth-account.tsx | 5 ++- .../document-signing-auth-page.tsx | 5 +-- apps/remix/app/root.tsx | 17 +++++---- apps/remix/app/routes/embed+/playground.tsx | 5 +-- apps/remix/public/site.webmanifest | 4 +-- apps/remix/react-router.config.ts | 5 +++ .../server/api/ai/detect-fields.client.ts | 3 +- .../server/api/ai/detect-recipients.client.ts | 3 +- apps/remix/server/main.js | 13 +++++++ apps/remix/server/router.ts | 4 ++- apps/remix/server/trpc/hono-trpc-open-api.ts | 6 ++-- apps/remix/server/trpc/hono-trpc-remix.ts | 7 +++- apps/remix/vite.config.ts | 4 +++ docker/Dockerfile | 6 ++++ packages/auth/client/index.ts | 6 ++-- .../lib/utils/handle-oauth-callback-url.ts | 17 +++++---- .../handle-oauth-organisation-callback-url.ts | 3 +- packages/auth/server/lib/utils/redirect.ts | 35 ++++++++++++++---- packages/ee/server-only/limits/client.ts | 5 ++- packages/lib/constants/app.ts | 36 +++++++++++++++++++ packages/lib/universal/get-base-url.ts | 4 +-- packages/lib/universal/upload/get-file.ts | 4 ++- packages/lib/universal/upload/put-file.ts | 3 +- packages/lib/utils/is-valid-return-to.ts | 30 ++++++++++++++-- .../common/language-switcher-dialog.tsx | 3 +- 27 files changed, 189 insertions(+), 50 deletions(-) diff --git a/apps/remix/app/components/forms/signin.tsx b/apps/remix/app/components/forms/signin.tsx index 16b9943ca..6270bc948 100644 --- a/apps/remix/app/components/forms/signin.tsx +++ b/apps/remix/app/components/forms/signin.tsx @@ -1,5 +1,6 @@ import { authClient } from '@documenso/auth/client'; import { AuthenticationErrorCode } from '@documenso/auth/server/lib/errors/error-codes'; +import { formatPath } from '@documenso/lib/constants/app'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; import { env } from '@documenso/lib/utils/env'; import { zEmail } from '@documenso/lib/utils/zod'; @@ -44,7 +45,7 @@ const handleFallbackErrorMessages = (code: string) => { return message; }; -const LOGIN_REDIRECT_PATH = '/'; +const LOGIN_REDIRECT_PATH = formatPath('/'); export const ZSignInFormSchema = z.object({ email: zEmail().min(1), diff --git a/apps/remix/app/components/general/app-command-menu.tsx b/apps/remix/app/components/general/app-command-menu.tsx index 3a704445e..7f245872f 100644 --- a/apps/remix/app/components/general/app-command-menu.tsx +++ b/apps/remix/app/components/general/app-command-menu.tsx @@ -1,5 +1,6 @@ import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value'; import { useSession } from '@documenso/lib/client-only/providers/session'; +import { formatPath } from '@documenso/lib/constants/app'; import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n'; import { DOCUMENTS_PAGE_SHORTCUT, @@ -862,7 +863,7 @@ const PromptLanguageCommands = ({ formData.append('lang', lang); - const response = await fetch('/api/locale', { + const response = await fetch(formatPath('/api/locale'), { method: 'post', body: formData, }); diff --git a/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx b/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx index 473166f7a..509cddced 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-auth-account.tsx @@ -1,4 +1,5 @@ import { authClient } from '@documenso/auth/client'; +import { formatPath } from '@documenso/lib/constants/app'; import { Alert, AlertDescription } from '@documenso/ui/primitives/alert'; import { Button } from '@documenso/ui/primitives/button'; import { DialogFooter } from '@documenso/ui/primitives/dialog'; @@ -34,7 +35,9 @@ export const DocumentSigningAuthAccount = ({ const currentPath = `${window.location.pathname}${window.location.search}${window.location.hash}`; await authClient.signOut({ - redirectPath: `/signin?returnTo=${encodeURIComponent(currentPath)}#embedded=true&email=${isDirectTemplate ? '' : email}`, + redirectPath: formatPath( + `/signin?returnTo=${encodeURIComponent(currentPath)}#embedded=true&email=${isDirectTemplate ? '' : email}`, + ), }); } catch { setIsSigningOut(false); diff --git a/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx b/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx index be9e409a0..a762eef57 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-auth-page.tsx @@ -1,4 +1,5 @@ import { authClient } from '@documenso/auth/client'; +import { formatPath } from '@documenso/lib/constants/app'; import { Button } from '@documenso/ui/primitives/button'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { msg } from '@lingui/core/macro'; @@ -21,10 +22,10 @@ export const DocumentSigningAuthPageView = ({ email, emailHasAccount }: Document try { setIsSigningOut(true); - let redirectPath = '/signin'; + let redirectPath = formatPath('/signin'); if (email) { - redirectPath = emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`; + redirectPath = emailHasAccount ? formatPath(`/signin#email=${email}`) : formatPath(`/signup#email=${email}`); } await authClient.signOut({ diff --git a/apps/remix/app/root.tsx b/apps/remix/app/root.tsx index a7c29b4be..75b37495c 100644 --- a/apps/remix/app/root.tsx +++ b/apps/remix/app/root.tsx @@ -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