The Epic Stack comes with a built-in server timing utility that allows you to
measure the performance of your application. You can find it in the
app/utils/timing.server.ts
file. The idea is you can wrap a function in a
time
call and then use the timings object to generate a Server-Timing
header
which you can then use to have fine grained timing metrics for requests made in
your app.
You can learn more about the Server Timing header on MDN. The metrics passed in this header will be visually displayed in the DevTools "Timing" tab.
Timings requires four parts:
- Setup Timings
- Time functions
- Create headers
- Send headers
Here are all those parts in action in the /user/:username/notes
route at the
time of this writing:
import {
combineServerTimings,
makeTimings,
time,
} from '#app/utils/timing.server.ts'
import { type Route } from './+types/notes.ts'
export async function loader({ params }: Route.LoaderArgs) {
const timings = makeTimings('notes loader') // <-- 1. Setup Timings
// 2. Time functions
const owner = await time(
() =>
prisma.user.findUnique({
where: {
username: params.username,
},
select: {
id: true,
username: true,
name: true,
imageId: true,
},
}),
{ timings, type: 'find user' },
)
if (!owner) {
throw new Response('Not found', { status: 404 })
}
// 2. Time functions
const notes = await time(
() =>
prisma.note.findMany({
where: {
ownerId: owner.id,
},
select: {
id: true,
title: true,
},
}),
{ timings, type: 'find notes' },
)
return json(
{ owner, notes },
{ headers: { 'Server-Timing': timings.toString() } }, // <-- 3. Create headers
)
}
// We have a general headers handler to save you from boilerplating.
export const headers: HeadersFunction = pipeHeaders
// this is basically what it does though
export const headers: Route.HeadersFunction = ({
loaderHeaders,
parentHeaders,
}) => {
return {
'Server-Timing': combineServerTimings(parentHeaders, loaderHeaders), // <-- 4. Send headers
}
}