Skip to content

Commit 30ab797

Browse files
committed
docs: Add comprehensive comparison of URL path approaches
- Compare app-based vs proxy-based path handling - Include test results for both approaches - Provide working Caddy and Nginx configurations - Recommend Approach 1 (main PR) for production This helps decide between the two architectural approaches.
1 parent 8e838dc commit 30ab797

File tree

1 file changed

+299
-0
lines changed

1 file changed

+299
-0
lines changed

APPROACH_COMPARISON.md

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
# URL Path Configuration: Approach Comparison
2+
3+
This document compares two approaches for handling `CMD_URL_PATH` configuration in CodiMD.
4+
5+
## Approach 1: Application Handles URL Path (Main PR #1943)
6+
7+
**Branch:** `bugfix/1936-404-errors-when-using-cmd_url_path-in-new-260`
8+
9+
### How It Works
10+
11+
1. App mounts routes and static assets at URL path prefix (e.g., `/codimd`)
12+
2. Express handles all path routing internally
13+
3. Works standalone or behind reverse proxy
14+
15+
### Configuration
16+
17+
**App (app.js):**
18+
```javascript
19+
const urlPathPrefix = config.urlPath ? `/${config.urlPath}` : ''
20+
app.use(urlPathPrefix + '/', express.static(...))
21+
app.use(urlPathPrefix, require('./lib/routes').router)
22+
```
23+
24+
**Reverse Proxy (Caddy):**
25+
```caddyfile
26+
:8080 {
27+
# Just pass everything through - app handles the path
28+
handle /codimd* {
29+
reverse_proxy localhost:3000
30+
}
31+
redir / /codimd/ 301
32+
}
33+
```
34+
35+
### Pros ✅
36+
37+
-**Works standalone** - No reverse proxy required
38+
-**Simpler proxy config** - Just pass through requests
39+
-**Flexible deployment** - Docker, Kubernetes, bare metal, etc.
40+
-**Backward compatible** - No breaking changes
41+
-**Single source of truth** - App controls its URL structure
42+
43+
### Cons ❌
44+
45+
-**More app code** - Logic in application layer
46+
-**Code duplication** - Some if/else blocks (though minimized)
47+
48+
---
49+
50+
## Approach 2: Reverse Proxy Handles URL Path (Experiment)
51+
52+
**Branch:** `experiment/reverse-proxy-path-rewriting`
53+
54+
### How It Works
55+
56+
1. App runs at root path (no URL path mounting)
57+
2. Reverse proxy strips path prefix before forwarding
58+
3. App still generates URLs with prefix (via serverURL config)
59+
60+
### Configuration
61+
62+
**App (app.js):**
63+
```javascript
64+
// No URL path mounting - runs at root
65+
app.use('/', express.static(...))
66+
app.use(require('./lib/routes').router)
67+
68+
// But serverURL still includes path for URL generation
69+
config.serverURL = 'http://localhost:3000/codimd'
70+
```
71+
72+
**Reverse Proxy (Caddy):**
73+
```caddyfile
74+
:8080 {
75+
# Redirect root
76+
route {
77+
@root path_regexp ^/$
78+
redir @root /codimd/ 301
79+
}
80+
81+
# Strip path and forward
82+
route /codimd/* {
83+
uri strip_prefix /codimd
84+
reverse_proxy localhost:3000 {
85+
header_up X-Forwarded-Prefix /codimd
86+
}
87+
}
88+
}
89+
```
90+
91+
### Pros ✅
92+
93+
-**Cleaner app code** - No URL path mounting logic
94+
-**Separation of concerns** - Path handling in proxy layer
95+
-**Proxy controls routing** - Easier to change paths without app changes
96+
97+
### Cons ❌
98+
99+
-**REQUIRES reverse proxy** - Won't work standalone
100+
-**Complex proxy config** - Must handle path stripping correctly
101+
-**Harder to debug** - Path transformation happens outside app
102+
-**Socket.IO complexity** - WebSocket path rewriting needed
103+
-**OAuth callback issues** - May need special handling
104+
-**Cookie path issues** - Session cookies may not work correctly
105+
106+
---
107+
108+
## Test Results
109+
110+
### Approach 1 (Main PR) - FULLY TESTED ✅
111+
112+
| Feature | Status | Notes |
113+
|---------|--------|-------|
114+
| Static assets | ✅ Pass | All assets load correctly |
115+
| Build bundles | ✅ Pass | Webpack assets working |
116+
| Routes | ✅ Pass | All routes accessible |
117+
| Redirects | ✅ Pass | No redirect loops |
118+
| Socket.IO | ✅ Pass | WebSocket connections work |
119+
| Standalone | ✅ Pass | Works without proxy |
120+
| With proxy | ✅ Pass | Works with Caddy/Nginx |
121+
122+
### Approach 2 (Experiment) - PARTIALLY TESTED ⚠️
123+
124+
| Feature | Status | Notes |
125+
|---------|--------|-------|
126+
| Static assets | ✅ Pass | Assets load via path stripping |
127+
| Build bundles | ✅ Pass | Webpack assets working |
128+
| Routes | ✅ Pass | Main routes accessible |
129+
| Redirects | ✅ Pass | Root and path redirects work |
130+
| Socket.IO | ⚠️ Unknown | Config present, not tested live |
131+
| Standalone | ❌ Fail | REQUIRES reverse proxy |
132+
| OAuth | ⚠️ Unknown | Not tested |
133+
| Cookies | ⚠️ Unknown | Session paths not verified |
134+
135+
---
136+
137+
## Recommendation
138+
139+
### For Production: Use Approach 1 (Main PR) ✅
140+
141+
**Why:**
142+
1. **Flexibility** - Works in any deployment scenario
143+
2. **Tested** - All features verified working
144+
3. **Simple** - Proxy config is straightforward
145+
4. **Reliable** - App has full control over routing
146+
147+
### For Experiment: Approach 2 is Viable ⚠️
148+
149+
**When to use:**
150+
- You always run behind a reverse proxy (Kubernetes ingress, etc.)
151+
- You want path routing completely separated from app
152+
- You're willing to handle edge cases (OAuth, WebSockets, cookies)
153+
154+
**When NOT to use:**
155+
- Need standalone deployment
156+
- Running locally for development
157+
- Can't guarantee reverse proxy presence
158+
159+
---
160+
161+
## Working Configurations
162+
163+
### Approach 1: Caddy with App Handling Path
164+
165+
```caddyfile
166+
{
167+
auto_https off
168+
}
169+
170+
:8080 {
171+
# App handles everything - just pass through
172+
handle /codimd* {
173+
reverse_proxy localhost:3000
174+
}
175+
176+
redir / /codimd/ 301
177+
178+
log {
179+
output stdout
180+
level INFO
181+
}
182+
}
183+
```
184+
185+
### Approach 2: Caddy with Path Stripping
186+
187+
```caddyfile
188+
{
189+
auto_https off
190+
}
191+
192+
:8080 {
193+
log {
194+
output stdout
195+
level INFO
196+
}
197+
198+
# Redirect root
199+
route {
200+
@root path_regexp ^/$
201+
redir @root /codimd/ 301
202+
}
203+
204+
# Redirect /codimd to /codimd/
205+
route {
206+
@codimd_exact path_regexp ^/codimd$
207+
redir @codimd_exact /codimd/ 301
208+
}
209+
210+
# Handle Socket.IO
211+
route /codimd/socket.io/* {
212+
uri strip_prefix /codimd
213+
reverse_proxy localhost:3000 {
214+
header_up X-Forwarded-Prefix /codimd
215+
}
216+
}
217+
218+
# Handle all other /codimd/* requests
219+
route /codimd/* {
220+
uri strip_prefix /codimd
221+
reverse_proxy localhost:3000 {
222+
header_up X-Forwarded-Prefix /codimd
223+
}
224+
}
225+
}
226+
```
227+
228+
---
229+
230+
## Nginx Equivalent Configs
231+
232+
### Approach 1: Nginx with App Handling Path
233+
234+
```nginx
235+
server {
236+
listen 8080;
237+
server_name localhost;
238+
239+
# App handles everything - just pass through
240+
location /codimd {
241+
proxy_pass http://localhost:3000;
242+
proxy_set_header Host $host;
243+
proxy_set_header X-Real-IP $remote_addr;
244+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
245+
proxy_set_header X-Forwarded-Proto $scheme;
246+
}
247+
248+
# Redirect root
249+
location = / {
250+
return 301 /codimd/;
251+
}
252+
}
253+
```
254+
255+
### Approach 2: Nginx with Path Stripping
256+
257+
```nginx
258+
server {
259+
listen 8080;
260+
server_name localhost;
261+
262+
# Redirect root
263+
location = / {
264+
return 301 /codimd/;
265+
}
266+
267+
# Redirect /codimd to /codimd/
268+
location = /codimd {
269+
return 301 /codimd/;
270+
}
271+
272+
# Strip /codimd and forward
273+
location /codimd/ {
274+
rewrite ^/codimd/(.*)$ /$1 break;
275+
proxy_pass http://localhost:3000;
276+
proxy_set_header Host $host;
277+
proxy_set_header X-Real-IP $remote_addr;
278+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
279+
proxy_set_header X-Forwarded-Proto $scheme;
280+
proxy_set_header X-Forwarded-Prefix /codimd;
281+
}
282+
}
283+
```
284+
285+
---
286+
287+
## Conclusion
288+
289+
**✅ Recommend Approach 1 (Main PR #1943)** for production use:
290+
- Proven to work in all scenarios
291+
- Simpler to deploy and maintain
292+
- No reverse proxy dependency
293+
294+
**⚠️ Approach 2 (Experiment)** is interesting but has limitations:
295+
- Successfully demonstrates path stripping
296+
- Requires reverse proxy (not standalone)
297+
- Needs more testing for edge cases
298+
299+
Both approaches are technically valid, but Approach 1 offers better flexibility and has been thoroughly tested.

0 commit comments

Comments
 (0)