Skip to content

Commit 44e8cd4

Browse files
committed
new post 2025-12-06-running-multiple-apps-under-a-single-domain-with-vercel.md
1 parent c98364b commit 44e8cd4

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
---
2+
authors:
3+
- copdips
4+
categories:
5+
- frontend
6+
- web
7+
comments: true
8+
date:
9+
created: 2025-12-06
10+
---
11+
12+
# Running Multiple Apps Under a Single Domain with Vercel
13+
14+
I own the `copdips.com` domain, and need to point it at several Vercel apps so that:
15+
16+
* https://apps.copdips.com/app1 routes to https://app1.vercel.app
17+
* https://apps.copdips.com/app2 routes to https://app2.vercel.app
18+
* direct https://app1.vercel.app, still works as before
19+
* direct https://app2.vercel.app, still works as before
20+
21+
In case of redirect, if `app1.vercel.app` serves an HTML file that contains `<link href="/styles.css">`, the browser should load `apps.copdips.com/app1/styles.css`, but not `apps.copdips.com/styles.css` (the root).
22+
23+
This is a standard **Multi-Tenant Routing with Context Awareness** scenario. To serve both the root domain (`app1.vercel.app`) and the subpath version (`apps.copdips.com/app1`) from the same deployment, each app must become host-aware—effectively detecting whether the request comes through the main router or directly and adjusting asset URLs and links accordingly.
24+
25+
<!-- more -->
26+
27+
```mermaid
28+
graph TD
29+
User[User Browser]
30+
31+
subgraph Vercel_Edge_Network
32+
Router[Main Router Project<br/>apps.copdips.com]
33+
App1[Existing Project<br/>app1.vercel.app]
34+
App2[Existing Project<br/>app2.vercel.app]
35+
end
36+
37+
User -- requests apps.copdips.com/app1 --> Router
38+
User -- requests apps.copdips.com/app2 --> Router
39+
40+
Router -- "Rewrite (Invisible Proxy)" --> App1
41+
Router -- "Rewrite (Invisible Proxy)" --> App2
42+
```
43+
44+
## The Problem
45+
46+
When accessing `apps.copdips.com/app1/`, absolute paths like `/assets/style.css` resolve to `apps.copdips.com/assets/style.css` instead of `apps.copdips.com/app1/assets/style.css`. Result? 404 errors and broken navigation.
47+
48+
## The Solution
49+
50+
### Child Vercel App
51+
52+
1. Fix Asset Paths with Vite
53+
54+
Add one line to `vite.config.ts`:
55+
56+
```typescript title="vite.config.ts in Child App"
57+
export default defineConfig({
58+
plugins: [react()],
59+
base: './', // ⭐️ This makes all asset paths relative
60+
});
61+
```
62+
63+
2. Fix Navigation with Dynamic Basename (React Router Only)
64+
65+
**Only needed if your child app uses `react-router-dom`.** Your router needs to know it might be running under a subpath (`/my-app-name`) OR at the root (`/`).
66+
67+
Dynamically set the basename in your Router:
68+
69+
```tsx title="App.tsx in Child App"
70+
import { BrowserRouter } from 'react-router-dom';
71+
72+
const App = () => {
73+
const getBasename = () => {
74+
const path = window.location.pathname;
75+
if (path.startsWith('/my-app-name')) return '/my-app-name';
76+
return '/'; // Direct access (my-app.vercel.app)
77+
};
78+
79+
return (
80+
<BrowserRouter basename={getBasename()}>
81+
{/* Your routes here */}
82+
</BrowserRouter>
83+
);
84+
};
85+
```
86+
87+
Now `<Link to="/about">` works correctly in both environments.
88+
89+
### Router Part
90+
91+
On the main domain (`apps.copdips.com`), configure `vercel.json`:
92+
93+
1. For React/SPA Apps
94+
95+
```json title="vercel.json in Router App"
96+
{
97+
"trailingSlash": true,
98+
"rewrites": [
99+
{ "source": "/app1/:path*", "destination": "https://app1.vercel.app/:path*" }
100+
],
101+
"redirects": [
102+
{ "source": "/app1", "destination": "/app1/", "permanent": true }
103+
]
104+
}
105+
```
106+
107+
2. For MkDocs Apps
108+
109+
If your MkDocs site uses `use_directory_urls: true` (hides `.html`), you need explicit `index.html` rewrites:
110+
111+
```json title="vercel.json in Router App for MkDocs"
112+
{
113+
"trailingSlash": true,
114+
"rewrites": [
115+
{
116+
"source": "/my-docs/",
117+
"destination": "https://my-docs.vercel.app/index.html"
118+
},
119+
{
120+
"source": "/my-docs/:path*/",
121+
"destination": "https://my-docs.vercel.app/:path*/index.html"
122+
},
123+
{
124+
"source": "/my-docs/:path*",
125+
"destination": "https://my-docs.vercel.app/:path*"
126+
}
127+
]
128+
}
129+
```
130+
131+
If using `use_directory_urls: false` (shows `.html`), use the simple SPA config aboveno special `index.html` handling needed.
132+
133+
**Note:** `trailingSlash: true` in the router's `vercel.json` works for both React and MkDocs apps.
134+
135+
## Why This Works
136+
137+
**Relative paths** (`base: './'`) make assets load from the current directory, not the domain root.
138+
139+
**Dynamic basename** tells React Router where the app lives, so internal links stay scoped correctly.
140+
141+
**Result:** One deployment, multiple URLs. Simple.
142+
143+
---
144+
145+
## Real Example
146+
147+
My [OAuth2/OIDC Visualizer](https://apps.copdips.com/oauth2-oidc-visualizer/) works at both:
148+
149+
* `oauth2-oidc-visualizer.vercel.app` (direct)
150+
* `apps.copdips.com/oauth2-oidc-visualizer` (proxied)
151+
152+
Same code, zero duplication. Perfect for portfolios, microservices, or multi-tenant apps.

0 commit comments

Comments
 (0)