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
@@ -1,3 +1,4 @@
import { formatPath } from '@documenso/lib/constants/app';
import { z } from 'zod';
import { type TDetectFieldsRequest, ZNormalizedFieldWithContextSchema } from './detect-fields.types';
@@ -69,7 +70,7 @@ export const detectFields = async ({
onError,
signal,
}: DetectFieldsOptions): Promise<void> => {
const response = await fetch('/api/ai/detect-fields', {
const response = await fetch(formatPath('/api/ai/detect-fields'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -1,3 +1,4 @@
import { formatPath } from '@documenso/lib/constants/app';
import { ZDetectedRecipientSchema } from '@documenso/lib/server-only/ai/envelope/detect-recipients/schema';
import { z } from 'zod';
@@ -70,7 +71,7 @@ export const detectRecipients = async ({
onError,
signal,
}: DetectRecipientsOptions): Promise<void> => {
const response = await fetch('/api/ai/detect-recipients', {
const response = await fetch(formatPath('/api/ai/detect-recipients'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
+13
View File
@@ -14,9 +14,22 @@ import { getLoadContext } from './hono/server/load-context.js';
import server from './hono/server/router.js';
import * as build from './index.js';
// Sub-path the app is served under (e.g. "/ESign"). Empty = root.
// Must match the basePath used by the Hono router and the Vite `base`/RR
// `basename` so that hashed asset URLs like `/ESign/assets/app-xxx.css`
// resolve to files on disk at `build/client/assets/app-xxx.css`.
const basePath = (process.env.NEXT_PUBLIC_BASE_PATH ?? '').replace(/\/$/, '');
server.use(
serveStatic({
root: 'build/client',
rewriteRequestPath: (path) => {
if (basePath && (path === basePath || path.startsWith(`${basePath}/`))) {
const stripped = path.slice(basePath.length);
return stripped === '' ? '/' : stripped;
}
return path;
},
onFound: (path, c) => {
if (path.startsWith('build/client/assets')) {
// Hard cache assets with hashed file names.
+3 -1
View File
@@ -46,7 +46,9 @@ export interface HonoEnv {
};
}
const app = new Hono<HonoEnv>();
const basePath = (env('NEXT_PUBLIC_BASE_PATH') ?? '').replace(/\/$/, '');
const app = new Hono<HonoEnv>().basePath(basePath || '/');
/**
* Database-backed rate limiting for API routes.
+4 -2
View File
@@ -1,4 +1,4 @@
import { API_V2_BETA_URL, API_V2_URL } from '@documenso/lib/constants/app';
import { API_V2_BETA_URL, API_V2_URL, formatPath } from '@documenso/lib/constants/app';
import { AppError, genericErrorCodeToTrpcErrorCodeMap } from '@documenso/lib/errors/app-error';
import { createTrpcContext } from '@documenso/trpc/server/context';
import { appRouter } from '@documenso/trpc/server/router';
@@ -11,8 +11,10 @@ type OpenApiTrpcServerHandlerOptions = {
};
export const openApiTrpcServerHandler = async (c: Context, { isBeta }: OpenApiTrpcServerHandlerOptions) => {
const endpoint = formatPath(isBeta ? API_V2_BETA_URL : API_V2_URL) as `/${string}`;
return createOpenApiFetchHandler<typeof appRouter>({
endpoint: isBeta ? API_V2_BETA_URL : API_V2_URL,
endpoint,
router: appRouter,
createContext: async () => createTrpcContext({ c, requestSource: 'apiV2' }),
req: c.req.raw,
+6 -1
View File
@@ -1,3 +1,4 @@
import { formatPath } from '@documenso/lib/constants/app';
import { createTrpcContext } from '@documenso/trpc/server/context';
import { appRouter } from '@documenso/trpc/server/router';
import { handleTrpcRouterError } from '@documenso/trpc/utils/trpc-error-handler';
@@ -5,10 +6,14 @@ import { trpcServer } from '@hono/trpc-server';
/**
* Trpc server for internal routes like /api/trpc/*
*
* `endpoint` must include the sub-path prefix (e.g. "/ESign") because the
* @hono/trpc-server adapter slices the prefix off the full URL pathname to
* compute the procedure name. Hono's `basePath` doesn't rewrite the URL.
*/
export const reactRouterTrpcServer = trpcServer({
router: appRouter,
endpoint: '/api/trpc',
endpoint: formatPath('/api/trpc'),
createContext: async (_, c) => createTrpcContext({ c, requestSource: 'app' }),
onError: (opts) => handleTrpcRouterError(opts, 'trpc'),
});