diff --git a/docs/api/remix.md b/docs/api/remix.md index 6b036306cf0..2d6a72ecbdd 100644 --- a/docs/api/remix.md +++ b/docs/api/remix.md @@ -1555,14 +1555,13 @@ These are fully featured utilities for handling fairly simple use cases. It's no **Example:** ```tsx -const uploadHandler = unstable_createFileUploadHandler({ - maxFileSize: 5_000_000, - file: ({ filename }) => filename, -}); - export const action: ActionFunction = async ({ request, }) => { + const uploadHandler = unstable_createFileUploadHandler({ + maxFileSize: 5_000_000, + file: ({ filename }) => filename, + }); const formData = await unstable_parseMultipartFormData( request, uploadHandler @@ -1594,13 +1593,12 @@ The `filter` function accepts an `object` and returns a `boolean` (or a promise **Example:** ```tsx -const uploadHandler = unstable_createMemoryUploadHandler({ - maxFileSize: 500_000, -}); - export const action: ActionFunction = async ({ request, }) => { + const uploadHandler = unstable_createMemoryUploadHandler({ + maxFileSize: 500_000, + }); const formData = await unstable_parseMultipartFormData( request, uploadHandler @@ -1709,12 +1707,12 @@ Your job is to do whatever you need with the `stream` and return a value that's We have the built-in `unstable_createFileUploadHandler` and `unstable_createMemoryUploadHandler` and we also expect more upload handler utilities to be developed in the future. If you have a form that needs to use different upload handlers, you can compose them together with a custom handler, here's a theoretical example: -```tsx +```tsx filename=file-upload-handler.server.tsx import type { UploadHandler } from "@remix-run/{runtime}"; import { unstable_createFileUploadHandler } from "@remix-run/{runtime}"; import { createCloudinaryUploadHandler } from "some-handy-remix-util"; -export const fileUploadHandler = +export const standardFileUploadHandler = unstable_createFileUploadHandler({ directory: "public/calendar-events", }); @@ -1724,9 +1722,9 @@ export const cloudinaryUploadHandler = folder: "/my-site/avatars", }); -export const multHandler: UploadHandler = (args) => { +export const fileUploadHandler: UploadHandler = (args) => { if (args.name === "calendarEvent") { - return fileUploadHandler(args); + return standardFileUploadHandler(args); } else if (args.name === "eventBanner") { return cloudinaryUploadHandler(args); } else { diff --git a/docs/pages/gotchas.md b/docs/pages/gotchas.md index 9d0d7084966..88bda11ebd3 100644 --- a/docs/pages/gotchas.md +++ b/docs/pages/gotchas.md @@ -55,6 +55,31 @@ export default function SomeRoute() { Even better, send a PR to the project to add `"sideEffects": false` to their package.json so that bundlers that tree shake know they can safely remove the code from browser bundles. +Similarly, you may run into the same error if you call a function at the top-level scope of your route module that depends on server-only code. + +For example, [Remix upload handlers like `unstable_createFileUploadHandler` and `unstable_createMemoryUploadHandler`](../api/remix#uploadhandler) use Node globals under the hood and should only be called on the server. You can call either of these functions in a `*.server.js` or `*.server.ts` file, or you can move them into your route's `action` or `loader` function: + +```tsx filename=app/routes/some-route.jsx +import { unstable_createFileUploadHandler } from "@remix-run/{runtime}"; + +// Instead of this… +const uploadHandler = unstable_createFileUploadHandler({ + maxFileSize: 5_000_000, + file: ({ filename }) => filename, +}); + +// …do this + +export async function action({ request }) { + const uploadHandler = unstable_createFileUploadHandler({ + maxFileSize: 5_000_000, + file: ({ filename }) => filename, + }); + + // ... +} +``` + > Why does this happen? Remix uses "tree shaking" to remove server code from browser bundles. Anything inside of Route module `loader`, `action`, and `headers` exports will be removed. It's a great approach but suffers from ecosystem compatibility.