@@ -10,7 +10,7 @@ import statsd from '@/observability/lib/statsd'
1010import type { ExtendedRequest } from '@/types'
1111import { allVersions } from '@/versions/lib/all-versions'
1212import { minimumNotFoundHtml } from '../lib/constants'
13- import { defaultCacheControl } from './cache-control'
13+ import { contentTypeCacheControl , defaultCacheControl } from './cache-control'
1414import { isConnectionDropped } from './halt-on-dropped-connection'
1515import { nextHandleRequest } from './next'
1616
@@ -90,6 +90,12 @@ export default async function renderPage(req: ExtendedRequest, res: Response) {
9090 // Stop processing if the connection was already dropped
9191 if ( isConnectionDropped ( req , res ) ) return
9292
93+ // Content negotiation: serve markdown when the client prefers it over HTML.
94+ // Agents like Claude Code send Accept headers that omit text/html.
95+ if ( req . accepts ( [ 'text/html' , 'text/markdown' ] ) === 'text/markdown' ) {
96+ context . markdownRequested = true
97+ }
98+
9399 if ( ! req . context ) throw new Error ( 'request not contextualized' )
94100 req . context . renderedPage = await buildRenderedPage ( req )
95101 req . context . miniTocItems = buildMiniTocItems ( req )
@@ -145,15 +151,11 @@ export default async function renderPage(req: ExtendedRequest, res: Response) {
145151 }
146152
147153 if ( context . markdownRequested ) {
148- if ( ! page . autogenerated && page . documentType === 'article' ) {
149- return res . type ( 'text/markdown' ) . send ( req . context . renderedPage )
150- } else {
151- const newUrl = req . originalUrl . replace ( req . path , req . path . replace ( / \. m d $ / , '' ) )
152- return res . redirect ( newUrl )
153- }
154+ contentTypeCacheControl ( res )
155+ return res . type ( 'text/markdown' ) . send ( req . context . renderedPage )
154156 }
155157
156- defaultCacheControl ( res )
158+ contentTypeCacheControl ( res )
157159
158160 return nextHandleRequest ( req , res )
159161}
0 commit comments