Skip to content

Turn on Remix v3_singleFetch future flag#2708

Merged
wizardlyhel merged 25 commits into
mainfrom
hl-single-fetch
Feb 7, 2025
Merged

Turn on Remix v3_singleFetch future flag#2708
wizardlyhel merged 25 commits into
mainfrom
hl-single-fetch

Conversation

@wizardlyhel

@wizardlyhel wizardlyhel commented Jan 13, 2025

Copy link
Copy Markdown

WHY are these changes introduced?

Turn on Remix v3_singleFetch future flag

Upgrade steps

Remix single fetch migration quick guide: https://remix.run/docs/en/main/start/future-flags#v3_singlefetch
Remix single fetch migration guide: https://remix.run/docs/en/main/guides/single-fetch

Note: If you have any routes that appends (or looks for) a search param named _data, make sure to rename it to something else.

  1. In your vite.config.ts, add the single fetch future flag.

    +  declare module "@remix-run/server-runtime" {
    +    interface Future {
    +     v3_singleFetch: true;
    +    }
    +  }
    
      export default defineConfig({
        plugins: [
          hydrogen(),
          oxygen(),
          remix({
            presets: [hydrogen.preset()],
            future: {
              v3_fetcherPersist: true,
              v3_relativeSplatPath: true,
              v3_throwAbortReason: true,
              v3_lazyRouteDiscovery: true,
    +         v3_singleFetch: true,
            },
          }),
          tsconfigPaths(),
        ],
  2. In your entry.server.tsx, add nonce to the <RemixServer>.

    const body = await renderToReadableStream(
      <NonceProvider>
        <RemixServer
          context={remixContext}
          url={request.url}
    +     nonce={nonce}
        />
      </NonceProvider>,
  3. Update the shouldRevalidate function in root.tsx.

    Defaulting to no revalidation for root loader data to improve performance. When using this feature, you risk your UI getting out of sync with your server. Use with caution. If you are uncomfortable with this optimization, update the return false; to return defaultShouldRevalidate; instead.

    For more details see: https://remix.run/docs/en/main/route/should-revalidate

    export const shouldRevalidate: ShouldRevalidateFunction = ({
      formMethod,
      currentUrl,
      nextUrl,
    -  defaultShouldRevalidate,
    }) => {
      // revalidate when a mutation is performed e.g add to cart, login...
      if (formMethod && formMethod !== 'GET') return true;
    
      // revalidate when manually revalidating via useRevalidator
      if (currentUrl.toString() === nextUrl.toString()) return true;
    
    -  return defaultShouldRevalidate;
    +  return false;
    };
  4. Update cart.tsx to add a headers export and update to data import usage.

      import {
    -  json,
    +  data,
        type LoaderFunctionArgs,
        type ActionFunctionArgs,
        type HeadersFunction
      } from '@shopify/remix-oxygen';
    
    + export const headers: HeadersFunction = ({ actionHeaders }) => actionHeaders;
    
      export async function action({request, context}: ActionFunctionArgs) {
        ...
    -   return json(
    +   return data(
          {
            cart: cartResult,
            errors,
            warnings,
            analytics: {
              cartId,
            },
          },
          {status, headers},
        );
      }
    
      export async function loader({context}: LoaderFunctionArgs) {
        const {cart} = context;
    -    return json(await cart.get());
    +    return await cart.get();
      }
  5. Deprecate json and defer import usage from @shopify/remix-oxygen

    Remove json()/defer() in favor of raw objects.

    Single Fetch supports JSON objects and Promises out of the box, so you can return the raw data from your loader/action functions:

    - import {json} from "@shopify/remix-oxygen";
    
      export async function loader({}: LoaderFunctionArgs) {
        let tasks = await fetchTasks();
    -   return json(tasks);
    +   return tasks;
      }
    - import {defer} from "@shopify/remix-oxygen";
    
      export async function loader({}: LoaderFunctionArgs) {
        let lazyStuff = fetchLazyStuff();
        let tasks = await fetchTasks();
    -   return defer({ tasks, lazyStuff });
    +   return { tasks, lazyStuff };
      }

    If you were using the second parameter of json/defer to set a custom status or headers on your response, you can continue doing so via the new data API:

    -  import {json} from "@shopify/remix-oxygen";
    +  import {data, type HeadersFunction} from "@shopify/remix-oxygen";
    
    +  /**
    +   * If your loader or action is returning a response with headers,
    +   * make sure to export a headers function that merges your headers
    +   * on your route. Otherwise, your headers may be lost.
    +   * Remix doc: https://remix.run/docs/en/main/route/headers
    +   **/
    +  export const headers: HeadersFunction = ({ loaderHeaders }) => loaderHeaders;
    
      export async function loader({}: LoaderFunctionArgs) {
        let tasks = await fetchTasks();
    -    return json(tasks, {
    +    return data(tasks, {
          headers: {
            "Cache-Control": "public, max-age=604800"
          }
        });
      }
  6. If you are using legacy customer account flow or multipass, there are a couple more files that requires updating:

    In root.tsx and routes/account.tsx, add a headers export for loaderHeaders.

    + export const headers: HeadersFunction = ({loaderHeaders}) => loaderHeaders;

    In routes/account_.register.tsx, add a headers export for actionHeaders.

    + export const headers: HeadersFunction = ({actionHeaders}) => actionHeaders;
  7. If you are using multipass, in routes/account_.login.multipass.tsx

    a. export a headers export

    + export const headers: HeadersFunction = ({actionHeaders}) => actionHeaders;

    b. Update all json response wrapper to remixData

    import {
    - json,
    + data as remixData,
    } from '@shopify/remix-oxygen';
    
    -  return json(
    +  return remixData(
        ...
      );

WHAT is this pull request doing?

For all templates, remove json(), and defer() Remix response functions and use data() Remix response function when required

HOW to test your changes?

Make sure all templates are working properly without errors across all functions. Make sure:

  • Customers can login with customer account
  • All template examples are working
  • Oxygen deploys are working

Post-merge steps

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or noteworthy changes
  • I've added tests to cover my changes
  • I've added or updated the documentation

@shopify

shopify Bot commented Jan 13, 2025

Copy link
Copy Markdown
Contributor

Oxygen deployed a preview of your hl-single-fetch branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
custom-cart-method ✅ Successful (Logs) Preview deployment Inspect deployment February 7, 2025 7:50 PM
metaobjects ✅ Successful (Logs) Preview deployment Inspect deployment February 7, 2025 7:50 PM
classic-remix ✅ Successful (Logs) Preview deployment Inspect deployment February 7, 2025 7:50 PM
third-party-queries-caching ✅ Successful (Logs) Preview deployment Inspect deployment February 7, 2025 7:50 PM
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment February 7, 2025 7:50 PM

Learn more about Hydrogen's GitHub integration.

@wizardlyhel wizardlyhel marked this pull request as ready for review January 14, 2025 23:57
@github-actions

This comment has been minimized.

Comment on lines +62 to +74
/**
* Remix (single-fetch) request objects have different url
* paths when soft navigating. Examples:
*
* /_root.data - home page
* /collections.data - collections page
*
* These url annotations needs to be cleaned up before constructing urls to be passed as
* GET parameters for customer login url
*/
const cleanedPathname = pathname
.replace(/\.data$/, '')
.replace(/^\/_root$/, '/');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new behaviour covered in any of our tests?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh good catch - I'll get a test in

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests added

Comment thread templates/skeleton/app/routes/search.tsx Outdated
Comment thread docs/preview/vite.config.js Outdated
Helen Lin and others added 3 commits January 15, 2025 14:00

@juanpprieto juanpprieto left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks Helen. Tested skeleton and all good

@wizardlyhel wizardlyhel merged commit 4e81bd1 into main Feb 7, 2025
@wizardlyhel wizardlyhel deleted the hl-single-fetch branch February 7, 2025 19:54
juanpprieto pushed a commit that referenced this pull request Sep 17, 2025
Co-authored-by: Rheese <110670476+rbshop@users.noreply.github.com>
frandiox pushed a commit that referenced this pull request Jun 16, 2026
Co-authored-by: Rheese <110670476+rbshop@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants