Skip to content

Commit ef8c421

Browse files
kennethkalmerclaude
andcommitted
Add content negotiation for markdown documentation
Implements HTTP content negotiation in nginx to serve markdown versions of documentation pages based on the Accept header. This allows clients (like LLMs and text-based tools) to request markdown format while browsers continue to receive HTML by default. Key features: - Serves markdown for Accept: text/markdown, application/markdown, or text/plain - Maintains backward compatibility (HTML is default) - Works with existing authentication system - Supports both index and non-index file paths - No performance impact (uses nginx map blocks) Content negotiation behavior: - /docs/channels with Accept: text/markdown → serves docs/channels.md - /docs/channels with Accept: text/html → serves docs/channels/index.html - /docs/channels (browser default) → serves docs/channels/index.html - /docs/channels.md (direct access) → serves docs/channels.md Implementation: - Added text/markdown MIME type to config/mime.types - Added text/markdown to gzip_types for compression - Created map blocks to detect Accept header preferences - Updated location blocks to use content-negotiated file paths - Fallback to HTML when markdown doesn't exist All 211 markdown documentation files are now accessible via content negotiation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0620c36 commit ef8c421

File tree

2 files changed

+37
-6
lines changed

2 files changed

+37
-6
lines changed

config/mime.types

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ types {
1111

1212
text/mathml mml;
1313
text/plain txt;
14+
text/markdown md markdown;
1415
text/vnd.sun.j2me.app-descriptor jad;
1516
text/vnd.wap.wml wml;
1617
text/x-component htc;

config/nginx.conf.erb

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ http {
1818
gzip on;
1919
gzip_comp_level 6;
2020
gzip_min_length 512;
21-
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss font/woff font/woff2 image/svg+xml;
21+
gzip_types text/plain text/markdown text/css application/json application/javascript text/xml application/xml application/xml+rss font/woff font/woff2 image/svg+xml;
2222
gzip_vary on;
2323
gzip_proxied any; # Heroku router sends Via header
2424

@@ -62,6 +62,36 @@ http {
6262
<% end %>
6363
}
6464

65+
##
66+
# CONTENT NEGOTIATION FOR MARKDOWN
67+
# Maps Accept header to file extension preference
68+
69+
map $http_accept $docs_file_extension {
70+
default ".html";
71+
72+
# Exact markdown MIME types
73+
"text/markdown" ".md";
74+
"application/markdown" ".md";
75+
"text/plain" ".md";
76+
77+
# Handle multiple Accept values - prefer markdown if explicitly requested
78+
"~*text/markdown" ".md";
79+
"~*application/markdown" ".md";
80+
"~*^text/plain" ".md";
81+
82+
# Explicit HTML request gets HTML (handles browser defaults)
83+
"~*^text/html" ".html";
84+
"*/*" ".html";
85+
}
86+
87+
# Translate extension to file path
88+
map $docs_file_extension $docs_try_file {
89+
".html" "$request_uri/index.html";
90+
".md" "$request_uri.md";
91+
}
92+
93+
# / CONTENT NEGOTIATION FOR MARKDOWN
94+
6595
##
6696
# CORS CONFIGURATION
6797

@@ -231,10 +261,10 @@ http {
231261
<% if content_request_protected %>
232262
# Serve the file if it exists, otherwise try to authenticate
233263
# (.html requests won't match here, they'll go to the @html_auth location)
234-
try_files $request_uri @html_auth;
264+
try_files $request_uri $docs_try_file @html_auth;
235265
<% else %>
236-
# Serve the file if it exists, try index.html for paths without a trailing slash, otherwise 404
237-
try_files $request_uri $request_uri/index.html $request_uri/ =404;
266+
# Serve the file if it exists, try content-negotiated file, then index.html, otherwise 404
267+
try_files $request_uri $docs_try_file $request_uri/index.html $request_uri/ =404;
238268
<% end %>
239269
}
240270

@@ -252,8 +282,8 @@ http {
252282
<% end %>
253283
}
254284

255-
# If the request is authenticated, break out of the location block and serve the file
256-
try_files $request_uri.html $request_uri/index.html $request_uri/ =404;
285+
# If the request is authenticated, try content-negotiated file, then fallback to HTML
286+
try_files $request_uri.html $docs_try_file $request_uri/index.html $request_uri/ =404;
257287
}
258288

259289
# Don't serve files with the .html extension here, send them to the canonical location

0 commit comments

Comments
 (0)