Skip to content

Commit 013b128

Browse files
committed
Set as standalone server.
1 parent 70b51be commit 013b128

File tree

2 files changed

+118
-6
lines changed

2 files changed

+118
-6
lines changed

Dockerfile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ FROM node:22-alpine
1515

1616
WORKDIR /app
1717

18-
RUN npm install -g wrangler
19-
2018
COPY --from=builder /app/dist ./dist
19+
COPY --from=builder /app/server-standalone.js ./
2120
COPY --from=builder /app/package.json ./
22-
COPY --from=builder /app/wrangler.jsonc ./
2321

24-
EXPOSE 3000
22+
ENV PORT=3000
23+
ENV HOST=0.0.0.0
24+
ENV NODE_ENV=production
2525

26-
WORKDIR /app/dist/server
26+
EXPOSE 3000
2727

28-
CMD ["wrangler", "dev", "--port", "3000", "--ip", "0.0.0.0"]
28+
CMD ["node", "server-standalone.js"]

server-standalone.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Standalone Node.js server for TanStack Start application
5+
* This wraps the fetch handler in a simple HTTP server
6+
*/
7+
8+
import { createServer } from 'node:http'
9+
import server from './dist/server/server.js'
10+
11+
const PORT = process.env.PORT || 3000
12+
const HOST = process.env.HOST || '0.0.0.0'
13+
14+
// Create HTTP server
15+
const httpServer = createServer(async (req, res) => {
16+
try {
17+
// Build the full URL
18+
const protocol = req.connection.encrypted ? 'https' : 'http'
19+
const url = new URL(req.url, `${protocol}://${req.headers.host}`)
20+
21+
// Convert Node.js request headers to Web Headers
22+
const headers = new Headers()
23+
for (const [key, value] of Object.entries(req.headers)) {
24+
if (value !== undefined) {
25+
if (Array.isArray(value)) {
26+
value.forEach(v => headers.append(key, v))
27+
} else {
28+
headers.append(key, value)
29+
}
30+
}
31+
}
32+
33+
// Get request body for non-GET/HEAD requests
34+
const body = req.method !== 'GET' && req.method !== 'HEAD'
35+
? await new Promise((resolve, reject) => {
36+
const chunks = []
37+
req.on('data', (chunk) => chunks.push(chunk))
38+
req.on('end', () => resolve(Buffer.concat(chunks)))
39+
req.on('error', reject)
40+
})
41+
: undefined
42+
43+
// Create Web Request
44+
const request = new Request(url, {
45+
method: req.method,
46+
headers,
47+
body: body && body.length > 0 ? body : undefined,
48+
})
49+
50+
// Call the TanStack Start fetch handler
51+
const response = await server.fetch(request)
52+
53+
// Convert Web Response to Node.js response
54+
res.statusCode = response.status
55+
56+
// Copy response headers
57+
response.headers.forEach((value, key) => {
58+
res.setHeader(key, value)
59+
})
60+
61+
// Stream the response body
62+
if (response.body) {
63+
const reader = response.body.getReader()
64+
const pump = async () => {
65+
while (true) {
66+
const { done, value } = await reader.read()
67+
if (done) {
68+
res.end()
69+
break
70+
}
71+
if (!res.write(value)) {
72+
await new Promise(resolve => res.once('drain', resolve))
73+
}
74+
}
75+
}
76+
await pump()
77+
} else {
78+
res.end()
79+
}
80+
} catch (error) {
81+
console.error('Server error:', error)
82+
if (!res.headersSent) {
83+
res.statusCode = 500
84+
res.setHeader('Content-Type', 'text/plain')
85+
}
86+
if (!res.writableEnded) {
87+
res.end('Internal Server Error')
88+
}
89+
}
90+
})
91+
92+
httpServer.listen(PORT, HOST, () => {
93+
console.log(`✓ Server running at http://${HOST}:${PORT}`)
94+
})
95+
96+
// Graceful shutdown
97+
const gracefulShutdown = (signal) => {
98+
console.log(`${signal} received, closing server...`)
99+
httpServer.close(() => {
100+
console.log('Server closed')
101+
process.exit(0)
102+
})
103+
104+
// Force close after 10 seconds
105+
setTimeout(() => {
106+
console.error('Could not close connections in time, forcefully shutting down')
107+
process.exit(1)
108+
}, 10000)
109+
}
110+
111+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'))
112+
process.on('SIGINT', () => gracefulShutdown('SIGINT'))

0 commit comments

Comments
 (0)