@@ -8,6 +8,7 @@ import { createRequestHandler } from "@react-router/express";
88import { createRequestListener } from "@remix-run/node-fetch-server" ;
99import compression from "compression" ;
1010import express from "express" ;
11+ import type { RequestHandler as ExpressRequestHandler } from "express" ;
1112import morgan from "morgan" ;
1213import sourceMapSupport from "source-map-support" ;
1314import getPort from "get-port" ;
@@ -52,12 +53,12 @@ type NormalizedBuild = {
5253function isRSCServerBuild ( build : unknown ) : build is RSCServerBuildModule {
5354 return Boolean (
5455 typeof build === "object" &&
55- build &&
56- "default" in build &&
57- typeof build . default === "object" &&
58- build . default &&
59- "fetch" in build . default &&
60- typeof build . default . fetch === "function" ,
56+ build &&
57+ "default" in build &&
58+ typeof build . default === "object" &&
59+ build . default &&
60+ "fetch" in build . default &&
61+ typeof build . default . fetch === "function" ,
6162 ) ;
6263}
6364
@@ -68,6 +69,20 @@ function parseNumber(raw?: string) {
6869 return maybe ;
6970}
7071
72+ function getExpressPath ( publicPath : string ) {
73+ // Vite allows `base` to be an absolute URL, but Express route paths must be
74+ // pathnames. Strip any origin before mounting static asset middleware.
75+ let pathname : string ;
76+
77+ try {
78+ pathname = new URL ( publicPath ) . pathname ;
79+ } catch {
80+ pathname = publicPath ;
81+ }
82+
83+ return pathname . startsWith ( "/" ) ? pathname : `/${ pathname } ` ;
84+ }
85+
7186async function run ( ) {
7287 let port = parseNumber ( process . env . PORT ) ?? ( await getPort ( { port : 3000 } ) ) ;
7388
@@ -103,7 +118,10 @@ async function run() {
103118 build = buildModule as ServerBuild ;
104119 }
105120
106- let onListen = ( ) => {
121+ let onListen = ( error : unknown ) => {
122+ if ( error ) {
123+ throw error ;
124+ }
107125 let address =
108126 process . env . HOST ||
109127 Object . values ( os . networkInterfaces ( ) )
@@ -124,29 +142,34 @@ async function run() {
124142 app . disable ( "x-powered-by" ) ;
125143
126144 if ( ! isRSCBuild ) {
127- app . use ( compression ( ) ) ;
145+ // `compression` may resolve to Express 4 types from transitive deps while
146+ // `react-router-serve` uses Express 5, but the runtime middleware signature
147+ // is compatible.
148+ app . use ( compression ( ) as unknown as ExpressRequestHandler ) ;
128149 }
129150
151+ let expressPublicPath = getExpressPath ( build . publicPath ) ;
152+
130153 app . use (
131- path . posix . join ( build . publicPath , "assets" ) ,
154+ path . posix . join ( expressPublicPath , "assets" ) ,
132155 express . static ( path . join ( build . assetsBuildDirectory , "assets" ) , {
133156 immutable : true ,
134157 maxAge : "1y" ,
135158 } ) ,
136159 ) ;
137- app . use ( build . publicPath , express . static ( build . assetsBuildDirectory ) ) ;
160+ app . use ( expressPublicPath , express . static ( build . assetsBuildDirectory ) ) ;
138161 app . use ( express . static ( "public" , { maxAge : "1h" } ) ) ;
139162 app . use ( morgan ( "tiny" ) ) ;
140163
141164 if ( build . fetch ) {
142- app . all ( "* " , createRequestListener ( build . fetch ) ) ;
165+ app . all ( "/{*splat} " , createRequestListener ( build . fetch ) ) ;
143166 } else {
144167 app . all (
145- "* " ,
168+ "/{*splat} " ,
146169 createRequestHandler ( {
147170 build : buildModule ,
148171 mode : process . env . NODE_ENV ,
149- } ) ,
172+ } ) as unknown as ExpressRequestHandler ,
150173 ) ;
151174 }
152175
0 commit comments