Skip to content

Commit 560585d

Browse files
committed
improve server timings and add them to the root loader
1 parent 92a93f1 commit 560585d

File tree

3 files changed

+82
-21
lines changed

3 files changed

+82
-21
lines changed

app/root.tsx

+40-15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type DataFunctionArgs,
66
type LinksFunction,
77
type V2_MetaFunction,
8+
type HeadersFunction,
89
} from '@remix-run/node'
910
import {
1011
Form,
@@ -30,6 +31,7 @@ import { getUserImgSrc } from './utils/misc.ts'
3031
import { useNonce } from './utils/nonce-provider.ts'
3132
import { getSession, getTheme } from './utils/session.server.ts'
3233
import { useOptionalUser, useUser } from './utils/user.ts'
34+
import { makeTimings, time } from './utils/timing.server.ts'
3335

3436
export const links: LinksFunction = () => {
3537
return [
@@ -67,13 +69,22 @@ export const meta: V2_MetaFunction = () => {
6769

6870
export async function loader({ request }: DataFunctionArgs) {
6971
const cookieSession = await getSession(request.headers.get('Cookie'))
70-
const userId = await getUserId(request)
72+
const timings = makeTimings('rootLoader')
73+
const userId = await time(() => getUserId(request), {
74+
timings,
75+
type: 'getUserId',
76+
desc: 'getUserId in root',
77+
})
7178

7279
const user = userId
73-
? await prisma.user.findUnique({
74-
where: { id: userId },
75-
select: { id: true, name: true, username: true, imageId: true },
76-
})
80+
? await time(
81+
() =>
82+
prisma.user.findUnique({
83+
where: { id: userId },
84+
select: { id: true, name: true, username: true, imageId: true },
85+
}),
86+
{ timings, type: 'find user', desc: 'find user in root' },
87+
)
7788
: null
7889
if (userId && !user) {
7990
console.info('something weird happened')
@@ -82,18 +93,32 @@ export async function loader({ request }: DataFunctionArgs) {
8293
await authenticator.logout(request, { redirectTo: '/' })
8394
}
8495

85-
return json({
86-
user,
87-
requestInfo: {
88-
hints: getHints(request),
89-
origin: getDomainUrl(request),
90-
path: new URL(request.url).pathname,
91-
session: {
92-
theme: getTheme(cookieSession),
96+
return json(
97+
{
98+
user,
99+
requestInfo: {
100+
hints: getHints(request),
101+
origin: getDomainUrl(request),
102+
path: new URL(request.url).pathname,
103+
session: {
104+
theme: getTheme(cookieSession),
105+
},
93106
},
107+
ENV: getEnv(),
94108
},
95-
ENV: getEnv(),
96-
})
109+
{
110+
headers: {
111+
'Server-Timing': timings.toString(),
112+
},
113+
},
114+
)
115+
}
116+
117+
export const headers: HeadersFunction = ({ loaderHeaders }) => {
118+
const headers = {
119+
'Server-Timing': loaderHeaders.get('Server-Timing') ?? '',
120+
}
121+
return headers
97122
}
98123

99124
export default function App() {

app/routes/_marketing+/index.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
import type { V2_MetaFunction } from '@remix-run/node'
1+
import type { HeadersFunction, V2_MetaFunction } from '@remix-run/node'
22
import { logos, kodyRocket, stars } from './logos/logos.ts'
3+
import { combineServerTimings } from '~/utils/timing.server.ts'
34

45
export const meta: V2_MetaFunction = () => [{ title: 'Epic Notes' }]
56

7+
export const headers: HeadersFunction = ({ loaderHeaders, parentHeaders }) => {
8+
const headers = {
9+
'Server-Timing': combineServerTimings(parentHeaders, loaderHeaders),
10+
}
11+
return headers
12+
}
13+
614
export default function Index() {
715
return (
816
<main className="relative min-h-screen sm:flex sm:items-center sm:justify-center">

app/utils/timing.server.ts

+33-5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,27 @@ import { type CreateReporter } from 'cachified'
22

33
export type Timings = Record<
44
string,
5-
Array<{ desc?: string; type: string; time: number }>
5+
Array<
6+
{ desc?: string } & (
7+
| { time: number; start?: never }
8+
| { time?: never; start: number }
9+
)
10+
>
611
>
712

13+
export function makeTimings(type: string, desc?: string) {
14+
const timings: Timings = {
15+
[type]: [{ desc, start: performance.now() }],
16+
}
17+
Object.defineProperty(timings, 'toString', {
18+
value: function () {
19+
return getServerTimeHeader(timings)
20+
},
21+
enumerable: false,
22+
})
23+
return timings
24+
}
25+
826
function createTimer(type: string, desc?: string) {
927
const start = performance.now()
1028
return {
@@ -15,7 +33,7 @@ function createTimer(type: string, desc?: string) {
1533
// eslint-disable-next-line no-multi-assign
1634
timingType = timings[type] = []
1735
}
18-
timingType.push({ desc, type, time: performance.now() - start })
36+
timingType.push({ desc, time: performance.now() - start })
1937
},
2038
}
2139
}
@@ -42,18 +60,22 @@ export async function time<ReturnType>(
4260
return result
4361
}
4462

45-
export function getServerTimeHeader(timings: Timings) {
63+
export function getServerTimeHeader(timings?: Timings) {
64+
if (!timings) return ''
4665
return Object.entries(timings)
4766
.map(([key, timingInfos]) => {
4867
const dur = timingInfos
49-
.reduce((acc, timingInfo) => acc + timingInfo.time, 0)
68+
.reduce((acc, timingInfo) => {
69+
const time = timingInfo.time ?? performance.now() - timingInfo.start
70+
return acc + time
71+
}, 0)
5072
.toFixed(1)
5173
const desc = timingInfos
5274
.map(t => t.desc)
5375
.filter(Boolean)
5476
.join(' & ')
5577
return [
56-
key.replaceAll(/(:| |@|=|;|,)/g, '_'),
78+
key.replaceAll(/(:| |@|=|;|,|\/|\\)/g, '_'),
5779
desc ? `desc=${JSON.stringify(desc)}` : null,
5880
`dur=${dur}`,
5981
]
@@ -63,6 +85,12 @@ export function getServerTimeHeader(timings: Timings) {
6385
.join(',')
6486
}
6587

88+
export function combineServerTimings(headers1: Headers, headers2: Headers) {
89+
const newHeaders = new Headers(headers1)
90+
newHeaders.append('Server-Timing', headers2.get('Server-Timing') ?? '')
91+
return newHeaders.get('Server-Timing') ?? ''
92+
}
93+
6694
export function cachifiedTimingReporter<Value>(
6795
timings?: Timings,
6896
): undefined | CreateReporter<Value> {

0 commit comments

Comments
 (0)