Last night I worked on upgrading our project Starchart from Remix v1 to v2.
Update Remix to v2
#833
Fixes #808
- Switch from
ThrownResponse
toErrorResponse
- Switch from
@remix-run/node
'sResponse
to nativeResponse
- Use
Blob
to send the body as part ofResponse
- Use
- Switch from
LoaderArgs
toLoaderFunctionArgs
- Switch from
ActionArgs
toActionFunctionArgs
- Switch from
V2_MetaFunction
toMetaFunction
- Switch from
useActionData();
touseActionData<typeof action>();
- This is the method used in the docs.
- Assert type of
route.data
asRecord<string, unknown> | undefined
inuseMatchesData()
- This is the method used in this example which is linked to in the docs.
It was mostly pretty straightforward, thanks to most future flags being already enabled, which meant many breaking changes had already been adopted.
I followed a "2-minute walkthrough" linked in the upgrade documentation, which led to me trying to upgrade the whole thing at once instead of incrementally adopting the remaining feature flags. That aside the walkthrough was actually pretty helpful.
The upgrade was pretty standard stuff - changing some variable names and such - but there were a couple things took me a minute.
Adding
<typeof action>
touseActionData()
to make ituseActionData<typeof action>()
: I guess this wasn't a requirement in v1 even though the docs used<typeof action>
, so upgrading led to type-check errors. I took a quick look at the docs and added that bit there.Type-casting
useMatches()
: The type-cast in the code below (as
) wasn't in place when we were using v1 and upgrading led to type-check errors. I looked at an example from the docs for useMatches() and saw they used type-casting so I added it here too, and it fixed the type errors.
const matchingRoutes = useMatches();
useMemo(
() => matchingRoutes.find((route) => route.id === id),
[matchingRoutes, id]
) as { // This bit here
data: Record<string, unknown> | undefined;
};
- Mobile Safari browser
ReadableStream
bug: This one took me a while and I wouldn't have figured it out without AI. One of the breaking changes when moving to v2 required switching from@remix-run/node
's Response to the native Response. To do this I had to convert thebody
object passed to the Response to aReadableStream
. According to the Remix docs the normal way of doing this is usingPassThrough
andcreateReadableStreamFromReadable()
:
const body = new PassThrough();
new Response(createReadableStreamFromReadable(body), {
headers: responseHeaders,
status: responseStatusCode,
})
So when I had to update this section of code, I figured I should use PassThrough
too.
// Before
function createResponse(body: string | null, filename: string) {
if (!body) {
throw new Response('Unable to get certificate part', { status: 400 });
}
return new Response(body, {
status: 200,
headers: {
'Content-Type': 'application/x-pem-file',
}
});
}
// After
function createResponse(body: string | null, filename: string) {
if (!body) {
throw new Response('Unable to get certificate part', { status: 400 });
}
const readable = new PassThrough();
readable.push(body);
const stream = createReadableStreamFromReadable(readable);
return new Response(stream, {
status: 200,
headers: {
'Content-Type': 'application/x-pem-file',
'Content-Disposition': `attachment; filename=${filename}`,
},
});
}
But on Mobile Safari, this doesn't work. Something to do with how Safari implements ReadableStream. I saw the implementation tests broke and was able to figure out that this was the change that broke them, since it was the Download test that was failing and this was the only change to the download logic. But I only figured out the actual reason thanks to AI.
I fixed this by switching to a Blob, which has better compatibility with Mobile Safari.
// After After
function createResponse(body: string | null, filename: string) {
if (!body) {
throw new Response('Unable to get certificate part', { status: 400 });
}
const blob = new Blob([body], { type: 'application/x-pem-file' });
return new Response(blob.stream(), {
status: 200,
headers: {
'Content-Type': 'application/x-pem-file',
'Content-Disposition': `attachment; filename=${filename}`,
},
});
}
Interesting bug.
Coming Up Next
Next I want to try and migrate from the classic Remix compiler to Remix Vite and then to React Router v7.
Top comments (0)