diff --git a/contributors.yml b/contributors.yml index 54b907d220b..aed550d333c 100644 --- a/contributors.yml +++ b/contributors.yml @@ -2,6 +2,7 @@ - aaronshaf - abereghici - abotsi +- accidentaldeveloper - adicuco - ahbruns - ahmedeldessouki @@ -11,6 +12,7 @@ - Alarid - alex-ketch - alexuxui +- amorriscode - andrelandgraf - andrewbrey - AndrewIngram @@ -28,12 +30,14 @@ - ascorbic - ashleyryan - ashocean +- athongsavath - axel-habermaier - BasixKOR - BenMcH - bmontalvo - bogas04 - BogdanDevBst +- bolchowka - brophdawg11 - bruno-oliveira - bsharrow @@ -140,6 +144,7 @@ - jmasson - jo-ninja - joaosamouco +- jodygeraldo - johannesbraeunig - johnson444 - johnson444 diff --git a/docs/api/remix.md b/docs/api/remix.md index 6b036306cf0..76c86d0cc3a 100644 --- a/docs/api/remix.md +++ b/docs/api/remix.md @@ -1270,9 +1270,9 @@ function SomeComponent() { ```js [ - { pathname, data, params, handle }, // root route - { pathname, data, params, handle }, // layout route - { pathname, data, params, handle }, // child route + { id, pathname, data, params, handle }, // root route + { id, pathname, data, params, handle }, // layout route + { id, pathname, data, params, handle }, // child route // etc. ]; ``` @@ -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/guides/constraints.md b/docs/guides/constraints.md index 3c369c20be7..5e94f65f2f4 100644 --- a/docs/guides/constraints.md +++ b/docs/guides/constraints.md @@ -160,7 +160,10 @@ export function removeTrailingSlash(loader) { return function (arg) { const { request } = arg; const url = new URL(request.url); - if (url.pathname.endsWith("/")) { + if ( + url.pathname !== "/" && + url.pathname.endsWith("/") + ) { return redirect(request.url.slice(0, -1), { status: 308, }); @@ -188,7 +191,7 @@ This type of abstraction is introduced to try to return a response early. Since import { redirect } from "@remix-run/{runtime}"; export function removeTrailingSlash(url) { - if (url.pathname.endsWith("/")) { + if (url.pathname !== "/" && url.pathname.endsWith("/")) { throw redirect(request.url.slice(0, -1), { status: 308, }); diff --git a/docs/pages/faq.md b/docs/pages/faq.md index def732141a4..864f39e2a93 100644 --- a/docs/pages/faq.md +++ b/docs/pages/faq.md @@ -228,3 +228,9 @@ Again, `formData.getAll()` is often all you need, we encourage you to give it a [form-data]: https://developer.mozilla.org/en-US/docs/Web/API/FormData [query-string]: https://www.npmjs.com/package/query-string + +## What's the difference between `CatchBoundary` & `ErrorBoundary`? + +Error boundaries render when your application throws an error and you had no clue it was going to happen. Most apps just go blank or have spinners spin forever. In remix the error boundary renders and you have granular control over it. + +Catch boundaries render when you decide in a loader that you can't proceed down the happy path to render the UI you want (auth required, record not found, etc.), so you throw a response and let some catch boundary up the tree handle it. diff --git a/docs/pages/gotchas.md b/docs/pages/gotchas.md index 76438fa5f87..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. @@ -120,3 +145,15 @@ if (typeof document === "undefined") { This will work for all JS environments (Node.js, Deno, Workers, etc.). [esbuild]: https://esbuild.github.io/ + +## Browser extensions injecting code + +You may run into this warning in the browser: + +``` +Warning: Did not expect server HTML to contain a