Use this file to discover all available pages before exploring further.
Remix packages work seamlessly across all JavaScript runtimes without modification. Write your code once and run it everywhere - Node.js, Bun, Deno, Cloudflare Workers, and any future runtime that implements web standards.
import { parseMultipartRequest } from 'remix/multipart-parser'async function handleUpload(request: Request): Promise<Response> { let files: Array<{ name: string; size: number }> = [] // This code works everywhere - no runtime-specific APIs for await (let part of parseMultipartRequest(request)) { if (part.isFile) { files.push({ name: part.filename, size: part.size, }) // Save file (implementation varies by runtime) await saveFile(part.filename, part.bytes) } } return Response.json({ files })}
// Works with: Bun, Deno, Cloudflare Workers, Node.js (with undici)import { parseMultipartRequest } from 'remix/multipart-parser'// Expects: Request with ReadableStream bodyfor await (let part of parseMultipartRequest(request)) { // ...}
// Works with: Node.js http.IncomingMessageimport { parseMultipartRequest } from 'remix/multipart-parser/node'// Expects: http.IncomingMessagehttp.createServer(async (req, res) => { for await (let part of parseMultipartRequest(req)) { // ... }})
The Node-specific entry point exists because Node’s http.IncomingMessage uses Node.js streams instead of Web Streams. The parsing logic is identical - only the input adapter differs.
The fetch-router package is completely runtime agnostic:
import { createRouter } from 'remix/fetch-router'import { route } from 'remix/fetch-router/routes'// This router works everywherelet routes = route({ api: { users: '/api/users', posts: '/api/posts', },})let router = createRouter()router.map(routes.api, { actions: { users: () => Response.json({ users: [] }), posts: () => Response.json({ posts: [] }), },})// Test with standard fetchlet response = await router.fetch('https://api.example.com/api/users')
Connect to any runtime:
Node.js
Bun
Deno
Cloudflare Workers
import * as http from 'node:http'import { createRequestListener } from 'remix/node-fetch-server'let server = http.createServer(createRequestListener(router.fetch))server.listen(3000)
import { openLazyFile } from 'remix/fs'// This code works in any runtimelet file = openLazyFile('./video.mp4')return new Response(file.stream(), { headers: { 'Content-Type': 'video/mp4', 'Content-Length': String(file.size), 'Accept-Ranges': 'bytes', },})
Node.js
Bun
Cloudflare Workers
import * as http from 'node:http'import { createRequestListener } from 'remix/node-fetch-server'import { openLazyFile } from 'remix/fs'async function handler(request: Request) { let file = openLazyFile('./video.mp4') return new Response(file.stream(), { headers: { 'Content-Type': 'video/mp4', 'Content-Length': String(file.size), }, })}http.createServer(createRequestListener(handler)).listen(3000)
import { openLazyFile } from 'remix/fs'Bun.serve({ async fetch(request) { let file = openLazyFile('./video.mp4') return new Response(file.stream(), { headers: { 'Content-Type': 'video/mp4', 'Content-Length': String(file.size), }, }) },})
export default { async fetch(request, env) { // Stream from R2 instead of filesystem let object = await env.BUCKET.get('video.mp4') return new Response(object.body, { headers: { 'Content-Type': 'video/mp4', 'Content-Length': String(object.size), }, }) },}