Skip to content

Commit ba09eea

Browse files
rsbhclaude
andcommitted
refactor: extract shared request handler from entry-prod
Moves API routing + SSR rendering logic into request-handler.ts so it can be reused by different deployment adapters. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a320792 commit ba09eea

2 files changed

Lines changed: 80 additions & 47 deletions

File tree

packages/chronicle/src/server/entry-prod.ts

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,22 @@ import { createServer } from 'http'
33
import { readFileSync, createReadStream } from 'fs'
44
import fsPromises from 'fs/promises'
55
import path from 'path'
6-
import React from 'react'
76
import { render } from './entry-server'
87
import { matchRoute } from './router'
98
import { loadConfig } from '@/lib/config'
109
import { loadApiSpecs } from '@/lib/openapi'
1110
import { getPage, loadPageComponent, buildPageTree } from '@/lib/source'
12-
import { mdxComponents } from '@/components/mdx'
11+
import { handleRequest } from './request-handler'
1312

1413
export { render, matchRoute, loadConfig, loadApiSpecs, getPage, loadPageComponent, buildPageTree }
1514

15+
async function writeResponse(res: import('http').ServerResponse, response: Response) {
16+
res.statusCode = response.status
17+
response.headers.forEach((value: string, key: string) => res.setHeader(key, value))
18+
const body = await response.text()
19+
res.end(body)
20+
}
21+
1622
export async function startServer(options: { port: number; distDir: string }) {
1723
const { port, distDir } = options
1824

@@ -23,19 +29,17 @@ export async function startServer(options: { port: number; distDir: string }) {
2329
const sirv = (await import('sirv')).default
2430
const assets = sirv(clientDir, { gzip: true })
2531

32+
const baseUrl = `http://localhost:${port}`
33+
2634
const server = createServer(async (req, res) => {
2735
const url = req.url || '/'
2836

2937
try {
30-
// API routes
31-
const routeHandler = matchRoute(new URL(url, `http://localhost:${port}`).href)
38+
// API routes — handled by shared request handler
39+
const routeHandler = matchRoute(new URL(url, baseUrl).href)
3240
if (routeHandler) {
33-
const request = new Request(new URL(url, `http://localhost:${port}`))
34-
const response = await routeHandler(request)
35-
res.statusCode = response.status
36-
response.headers.forEach((value: string, key: string) => res.setHeader(key, value))
37-
const body = await response.text()
38-
res.end(body)
41+
const response = await routeHandler(new Request(new URL(url, baseUrl)))
42+
await writeResponse(res, response)
3943
return
4044
}
4145

@@ -67,43 +71,9 @@ export async function startServer(options: { port: number; distDir: string }) {
6771
})
6872
if (assetHandled) return
6973

70-
// Resolve page data
71-
const pathname = new URL(url, `http://localhost:${port}`).pathname
72-
const slug = pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean)
73-
74-
const config = loadConfig()
75-
const apiSpecs = config.api?.length ? loadApiSpecs(config.api) : []
76-
77-
const [tree, sourcePage] = await Promise.all([
78-
buildPageTree(),
79-
getPage(slug),
80-
])
81-
82-
let pageData = null
83-
let embeddedData: any = { config, tree, slug, frontmatter: null, filePath: null }
84-
85-
if (sourcePage) {
86-
const component = await loadPageComponent(sourcePage)
87-
pageData = {
88-
slug,
89-
frontmatter: sourcePage.frontmatter,
90-
content: component ? React.createElement(component, { components: mdxComponents }) : null,
91-
}
92-
embeddedData.frontmatter = sourcePage.frontmatter
93-
embeddedData.filePath = sourcePage.filePath
94-
}
95-
96-
// SSR render
97-
const html = render(url, { config, tree, page: pageData, apiSpecs })
98-
99-
const dataScript = `<script>window.__PAGE_DATA__ = ${JSON.stringify(embeddedData)}</script>`
100-
const finalHtml = template
101-
.replace('<!--head-outlet-->', `<!--head-outlet-->${dataScript}`)
102-
.replace('<!--ssr-outlet-->', html)
103-
104-
res.setHeader('Content-Type', 'text/html')
105-
res.statusCode = 200
106-
res.end(finalHtml)
74+
// SSR render — handled by shared request handler
75+
const response = await handleRequest(url, { template, baseUrl })
76+
await writeResponse(res, response)
10777
} catch (e) {
10878
console.error(e)
10979
res.statusCode = 500
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Shared request handler for API routes + SSR rendering
2+
// Used by entry-prod.ts (Node) and entry-vercel.ts (Vercel)
3+
import React from 'react'
4+
import { render } from './entry-server'
5+
import { matchRoute } from './router'
6+
import { loadConfig } from '@/lib/config'
7+
import { loadApiSpecs } from '@/lib/openapi'
8+
import { getPage, loadPageComponent, buildPageTree } from '@/lib/source'
9+
import { mdxComponents } from '@/components/mdx'
10+
11+
export interface RequestHandlerOptions {
12+
template: string
13+
baseUrl: string
14+
}
15+
16+
export async function handleRequest(url: string, options: RequestHandlerOptions): Promise<Response> {
17+
const { template, baseUrl } = options
18+
const fullUrl = new URL(url, baseUrl).href
19+
20+
// API routes
21+
const routeHandler = matchRoute(fullUrl)
22+
if (routeHandler) {
23+
return routeHandler(new Request(fullUrl))
24+
}
25+
26+
// SSR render
27+
const pathname = new URL(url, baseUrl).pathname
28+
const slug = pathname === '/' ? [] : pathname.slice(1).split('/').filter(Boolean)
29+
30+
const config = loadConfig()
31+
const apiSpecs = config.api?.length ? loadApiSpecs(config.api) : []
32+
33+
const [tree, sourcePage] = await Promise.all([
34+
buildPageTree(),
35+
getPage(slug),
36+
])
37+
38+
let pageData = null
39+
let embeddedData: any = { config, tree, slug, frontmatter: null, filePath: null }
40+
41+
if (sourcePage) {
42+
const component = await loadPageComponent(sourcePage)
43+
pageData = {
44+
slug,
45+
frontmatter: sourcePage.frontmatter,
46+
content: component ? React.createElement(component, { components: mdxComponents }) : null,
47+
}
48+
embeddedData.frontmatter = sourcePage.frontmatter
49+
embeddedData.filePath = sourcePage.filePath
50+
}
51+
52+
const html = render(url, { config, tree, page: pageData, apiSpecs })
53+
54+
const dataScript = `<script>window.__PAGE_DATA__ = ${JSON.stringify(embeddedData)}</script>`
55+
const finalHtml = template
56+
.replace('<!--head-outlet-->', `<!--head-outlet-->${dataScript}`)
57+
.replace('<!--ssr-outlet-->', html)
58+
59+
return new Response(finalHtml, {
60+
status: 200,
61+
headers: { 'Content-Type': 'text/html' },
62+
})
63+
}

0 commit comments

Comments
 (0)