Documentation Index Fetch the complete documentation index at: https://mintlify.com/remix-run/remix/llms.txt
Use this file to discover all available pages before exploring further.
Remix is designed for performance from the ground up, built on web standards and runtime-agnostic code. Follow these guidelines to get the most out of your Remix applications.
Response Compression
Compress responses to reduce bandwidth:
import { createRouter } from 'remix/fetch-router'
import { compression } from 'remix/compression-middleware'
let router = createRouter ({
middleware: [
compression ({
threshold: 1024 , // Only compress responses > 1KB
level: 6 , // Compression level (1-9)
}),
],
})
Or compress specific responses:
import { compressResponse } from 'remix/response/compress'
router . get ( routes . data , async ({ request }) => {
let data = await fetchLargeDataset ()
let response = Response . json ( data )
return compressResponse ( response , request )
})
Caching Strategies
HTTP Caching
Use Cache-Control headers for browser caching:
import { CacheControl } from 'remix/headers'
router . get ( routes . assets , async ({ params }) => {
let file = await readFile ( params . filename )
return new Response ( file , {
headers: {
'Content-Type' : 'image/jpeg' ,
'Cache-Control' : CacheControl . stringify ({
public: true ,
maxAge: 31536000 , // 1 year
immutable: true ,
}),
},
})
})
ETag Support
Use ETags for conditional requests:
import { createFileResponse } from 'remix/response/file'
router . get ( routes . file , async ({ request , params }) => {
let file = await getFile ( params . id )
// createFileResponse automatically handles ETags and If-None-Match
return createFileResponse ( file , request )
})
Application-Level Caching
Cache expensive computations:
let cache = new Map < string , { value : any ; expires : number }>()
function cached < T >( key : string , fn : () => Promise < T >, ttl = 60000 ) : Promise < T > {
let cached = cache . get ( key )
if ( cached && cached . expires > Date . now ()) {
return Promise . resolve ( cached . value )
}
return fn (). then (( value ) => {
cache . set ( key , { value , expires: Date . now () + ttl })
return value
})
}
// Usage
router . get ( routes . stats , async () => {
let stats = await cached ( 'stats' , async () => {
return await computeExpensiveStats ()
}, 5 * 60 * 1000 ) // Cache for 5 minutes
return Response . json ( stats )
})
Database Query Optimization
Select Only Needed Columns
// Bad: Select all columns
let users = await db . query ( users ). all ()
// Good: Select only needed columns
let users = await db . query ( users )
. select ({
id: users . id ,
name: users . name ,
email: users . email ,
})
. all ()
Use Indexes
Add indexes for frequently queried columns:
import { createIndex } from 'remix/data-table/migrations'
let migration = {
up ( db ) {
createIndex ( db , 'users' , 'email' , { unique: true })
createIndex ( db , 'orders' , 'user_id' )
createIndex ( db , 'orders' , 'created_at' )
},
}
Batch Queries
Fetch related data in batches:
// Bad: N+1 query problem
let users = await db . query ( users ). all ()
for ( let user of users ) {
user . orders = await db . query ( orders ). where ({ userId: user . id }). all ()
}
// Good: Use relations for eager loading
let users = await db . query ( users )
. with ({ orders: userOrders })
. all ()
Limit Query Results
// Always paginate large result sets
let page = parseInt ( url . searchParams . get ( 'page' ) || '1' )
let perPage = 20
let users = await db . query ( users )
. orderBy ( 'created_at' , 'desc' )
. limit ( perPage )
. offset (( page - 1 ) * perPage )
. all ()
Streaming Responses
Stream large responses instead of buffering:
router . get ( routes . export , () => {
let stream = new ReadableStream ({
async start ( controller ) {
// Stream data in chunks
for await ( let chunk of generateLargeDataset ()) {
controller . enqueue ( encoder . encode ( JSON . stringify ( chunk ) + ' \n ' ))
}
controller . close ()
},
})
return new Response ( stream , {
headers: {
'Content-Type' : 'application/x-ndjson' ,
},
})
})
Static File Serving
Serve static files efficiently:
import { staticFiles } from 'remix/static-middleware'
let router = createRouter ({
middleware: [
staticFiles ( './public' , {
maxAge: 31536000 , // 1 year for immutable assets
immutable: true ,
// Only serve specific file types
filter : ( pathname ) => {
return / \. ( js | css | png | jpg | svg | woff2 ) $ / . test ( pathname )
},
}),
],
})
Minimize Re-renders
// Bad: Creates new objects on every render
function App ( handle : Handle ) {
return () => (
< div css = {{ color : 'red' , padding : '10px' }} >
Content
</ div >
)
}
// Good: Move static styles to setup
function App ( handle : Handle ) {
let styles = { color: 'red' , padding: '10px' }
return () => < div css ={ styles }> Content </ div >
}
Use Dynamic Styles Only When Needed
// Bad: Use css prop for dynamic values
function ProgressBar ( handle : Handle ) {
let progress = 0
return () => (
< div css = {{ width : ` ${ progress } %` }} > { /* Creates new CSS rule on every update */ }
{ progress } %
</ div >
)
}
// Good: Use style prop for dynamic values
function ProgressBar ( handle : Handle ) {
let progress = 0
return () => (
< div
css = {{ backgroundColor : 'blue' }} { /* Static */ }
style = {{ width : ` ${ progress } %` }} { /* Dynamic */ }
>
{ progress } %
</ div >
)
}
Batch Updates
// Bad: Multiple updates
function App ( handle : Handle ) {
let count = 0
let doubled = 0
return () => (
< button on = {{
click () {
count ++
handle . update () // First render
doubled = count * 2
handle . update () // Second render
},
}} >
{ count } / { doubled }
</ button >
)
}
// Good: Single update
function App ( handle : Handle ) {
let count = 0
let doubled = 0
return () => (
< button on = {{
click () {
count ++
doubled = count * 2
handle . update () // Single render
},
}} >
{ count } / { doubled }
</ button >
)
}
Request Timing
import { logger } from 'remix/logger-middleware'
let router = createRouter ({
middleware: [
logger ( '%date %method %path %status %response-time ms' ),
],
})
Custom Metrics
let requestDurations : number [] = []
function performanceMiddleware () : Middleware {
return async ( context , next ) => {
let start = performance . now ()
let response = await next ()
let duration = performance . now () - start
requestDurations . push ( duration )
// Calculate p95
if ( requestDurations . length > 100 ) {
let sorted = [ ... requestDurations ]. sort (( a , b ) => a - b )
let p95 = sorted [ Math . floor ( sorted . length * 0.95 )]
console . log ( `P95 response time: ${ p95 } ms` )
requestDurations = []
}
return response
}
}
Runtime-Specific Optimizations
Node.js
Use clustering to utilize all CPU cores
Enable HTTP/2 for multiplexing
Use --max-old-space-size for memory-intensive apps
Bun
Already fast by default
Use built-in SQLite for embedded databases
Leverage native APIs for maximum performance
Deno
Use KV for edge caching
Leverage Deno Deploy’s global network
Cache dependencies with DENO_DIR
Cloudflare Workers
Keep workers under 1MB
Use KV for persistent data
Leverage Durable Objects for stateful logic
Enable edge caching
Best Practices
Measure before optimizing
Compress responses
Implement caching at multiple levels
Optimize database queries
Stream large responses
Minimize component re-renders
Use appropriate status codes for caching
Monitor performance metrics
Compression Response compression middleware
Data Table Optimize database queries