// Production server for RSC // Run with: node server.js import { createServer } from 'node:http' import { readFile } from 'node:fs/promises' import { join, extname } from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = fileURLToPath(new URL('.', import.meta.url)) const PORT = process.env.PORT || 3000 // Import RSC handler - it exports { fetch: handler } const rscHandler = (await import('./dist/rsc/index.js')).default.fetch // MIME types const mimeTypes = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', } const server = createServer(async (req, res) => { const url = new URL(req.url, `http://localhost:${PORT}`) // Serve static client assets first (these don't go through RSC handler) if (url.pathname.startsWith('/dist/client') || url.pathname.startsWith('/assets') || url.pathname === '/vite.svg') { // Map paths to dist/client/* let distPath if (url.pathname.startsWith('/assets')) { distPath = join(__dirname, '/dist/client', url.pathname) } else if (url.pathname === '/vite.svg') { distPath = join(__dirname, '/dist/client/vite.svg') } else { distPath = join(__dirname, url.pathname) } try { const content = await readFile(distPath) const ext = extname(distPath) const contentType = mimeTypes[ext] || 'application/octet-stream' res.setHeader('Content-Type', contentType) res.end(content) } catch (err) { res.statusCode = 404 res.end('Not Found') } return } // All other routes use RSC handler (handles SSR, RSC, and dynamic pages) try { // Read the body first (for POST requests) const bodyContent = req.method !== 'GET' ? await new Promise((resolve, reject) => { const chunks = [] req.on('data', chunk => chunks.push(chunk)) req.on('end', () => resolve(Buffer.concat(chunks))) req.on('error', reject) }) : undefined // Clone the headers (req.headers is an object, not Map) const headers = new Headers() for (const key of Object.keys(req.headers)) { const value = req.headers[key] if (key.toLowerCase() !== 'content-length') { headers.set(key, value) } } // Create request with absolute URL (RSC handler requires full URL) const rscRequest = new Request(`http://localhost:${PORT}${url.pathname}${url.search}`, { method: req.method, headers, body: bodyContent, }) const response = await rscHandler(rscRequest) res.statusCode = response.status // Forward headers for (const [key, value] of response.headers.entries()) { if (key !== 'transfer-encoding' && key !== 'content-length') { res.setHeader(key, value) } } // For RSC stream, pipe directly if (response.headers.get('content-type')?.includes('x-component')) { response.body.pipe(res) return } // For HTML, inject correct client assets base path const html = await response.text() const modifiedHtml = html.replace( /import\("\/@id\/__x00__/g, 'import("/dist/client/@id/__x00__' ).replace( /href="\/src\//g, 'href="/dist/client/src/' ).replace( /src="\/src\//g, 'src="/dist/client/src/' ) res.setHeader('Content-Type', 'text/html') res.end(modifiedHtml) } catch (err) { console.error('RSC handler error:', err) res.statusCode = 500 res.end('Internal Server Error') } }) server.listen(PORT, () => { console.log(`RSC Server running at http://localhost:${PORT}/`) console.log(`Press Ctrl+C to stop`) })