diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx
index acf5b4782b20..a60364a5570f 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/entry.server.tsx
@@ -1,7 +1,7 @@
import '../instrument.server';
+import type { EntryContext } from 'react-router';
import { HandleErrorFunction, ServerRouter } from 'react-router';
import { createContentSecurityPolicy } from '@shopify/hydrogen';
-import type { EntryContext } from '@shopify/remix-oxygen';
import { renderToReadableStream } from 'react-dom/server';
import * as Sentry from '@sentry/react-router/cloudflare';
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/context.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/context.ts
new file mode 100644
index 000000000000..ed90c91d469e
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/context.ts
@@ -0,0 +1,29 @@
+import { createHydrogenContext } from '@shopify/hydrogen';
+import { AppSession } from '~/lib/session';
+import { CART_QUERY_FRAGMENT } from '~/lib/fragments';
+
+/**
+ * Creates Hydrogen context for React Router 7.x
+ */
+export async function createHydrogenRouterContext(request: Request, env: Env, executionContext: ExecutionContext) {
+ if (!env?.SESSION_SECRET) {
+ throw new Error('SESSION_SECRET environment variable is not set');
+ }
+
+ const waitUntil = executionContext.waitUntil.bind(executionContext);
+ const [cache, session] = await Promise.all([caches.open('hydrogen'), AppSession.init(request, [env.SESSION_SECRET])]);
+
+ const hydrogenContext = createHydrogenContext({
+ env,
+ request,
+ cache,
+ waitUntil,
+ session,
+ i18n: { language: 'EN', country: 'US' },
+ cart: {
+ queryFragment: CART_QUERY_FRAGMENT,
+ },
+ });
+
+ return hydrogenContext;
+}
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts
index 80d6e7b86b52..439891c74ae9 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/lib/session.ts
@@ -1,5 +1,5 @@
import type { HydrogenSession } from '@shopify/hydrogen';
-import { type Session, type SessionStorage, createCookieSessionStorage } from '@shopify/remix-oxygen';
+import { type Session, type SessionStorage, createCookieSessionStorage } from 'react-router';
/**
* This is a custom session implementation for your Hydrogen shop.
@@ -9,12 +9,17 @@ import { type Session, type SessionStorage, createCookieSessionStorage } from '@
export class AppSession implements HydrogenSession {
#sessionStorage;
#session;
+ #isPending = false;
constructor(sessionStorage: SessionStorage, session: Session) {
this.#sessionStorage = sessionStorage;
this.#session = session;
}
+ get isPending() {
+ return this.#isPending;
+ }
+
static async init(request: Request, secrets: string[]) {
const storage = createCookieSessionStorage({
cookie: {
@@ -48,6 +53,7 @@ export class AppSession implements HydrogenSession {
}
get set() {
+ this.#isPending = true;
return this.#session.set;
}
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx
index afa85270e045..27283806379a 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/root.tsx
@@ -1,5 +1,5 @@
import * as Sentry from '@sentry/react-router/cloudflare';
-import { type LoaderFunctionArgs } from '@shopify/remix-oxygen';
+import type { LoaderFunctionArgs } from 'react-router';
import {
Outlet,
isRouteErrorResponse,
@@ -9,8 +9,6 @@ import {
Scripts,
ScrollRestoration,
} from 'react-router';
-import { FOOTER_QUERY, HEADER_QUERY } from '~/lib/fragments';
-
import { useNonce } from '@shopify/hydrogen';
export type RootLoader = typeof loader;
@@ -57,17 +55,14 @@ export function links() {
}
export async function loader(args: LoaderFunctionArgs) {
- // Start fetching non-critical data without blocking time to first byte
- const deferredData = loadDeferredData(args);
-
- // Await the critical data required to render initial state of the page
- const criticalData = await loadCriticalData(args);
-
const { env } = args.context;
+ // Simplified loader for Sentry SDK testing - skip storefront queries
return {
- ...deferredData,
- ...criticalData,
+ header: null,
+ cart: null,
+ isLoggedIn: false,
+ footer: null,
ENV: {
sentryTrace: env.SENTRY_TRACE,
sentryBaggage: env.SENTRY_BAGGAGE,
@@ -77,61 +72,12 @@ export async function loader(args: LoaderFunctionArgs) {
checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN,
storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
withPrivacyBanner: false,
- // localize the privacy banner
- country: args.context.storefront.i18n.country,
- language: args.context.storefront.i18n.language,
+ country: 'US',
+ language: 'EN',
},
};
}
-/**
- * Load data necessary for rendering content above the fold. This is the critical data
- * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
- */
-async function loadCriticalData({ context }: LoaderFunctionArgs) {
- const { storefront } = context;
-
- const [header] = await Promise.all([
- storefront.query(HEADER_QUERY, {
- cache: storefront.CacheLong(),
- variables: {
- headerMenuHandle: 'main-menu', // Adjust to your header menu handle
- },
- }),
- // Add other queries here, so that they are loaded in parallel
- ]);
-
- return { header };
-}
-
-/**
- * Load data for rendering content below the fold. This data is deferred and will be
- * fetched after the initial page load. If it's unavailable, the page should still 200.
- * Make sure to not throw any errors here, as it will cause the page to 500.
- */
-function loadDeferredData({ context }: LoaderFunctionArgs) {
- const { storefront, customerAccount, cart } = context;
-
- // defer the footer query (below the fold)
- const footer = storefront
- .query(FOOTER_QUERY, {
- cache: storefront.CacheLong(),
- variables: {
- footerMenuHandle: 'footer', // Adjust to your footer menu handle
- },
- })
- .catch((error: any) => {
- // Log query errors, but don't throw them so the page can still render
- console.error(error);
- return null;
- });
- return {
- cart: cart.get(),
- isLoggedIn: customerAccount.isLoggedIn(),
- footer,
- };
-}
-
export function Layout({ children }: { children?: React.ReactNode }) {
const nonce = useNonce();
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx
index 1548c38084ad..006094a17f74 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/loader-error.tsx
@@ -1,5 +1,5 @@
import { useLoaderData } from 'react-router';
-import type { LoaderFunction } from '@shopify/remix-oxygen';
+import type { LoaderFunction } from 'react-router';
export default function LoaderError() {
useLoaderData();
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx
index 06ca3d7f2ae0..300e39fb6409 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/app/routes/navigate.tsx
@@ -1,7 +1,8 @@
import { useLoaderData } from 'react-router';
-import type { LoaderFunction } from '@shopify/remix-oxygen';
+import type { LoaderFunction } from 'react-router';
-export const loader: LoaderFunction = async ({ params: { id } }) => {
+export const loader: LoaderFunction = async ({ params }) => {
+ const { id } = params as { id: string };
if (id === '-1') {
throw new Error('Unexpected Server Error');
}
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts
index ce37d9f3c464..8577d306258c 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/env.d.ts
@@ -1,5 +1,4 @@
///
-///
///
// Enhance TypeScript's built-in typings.
@@ -26,12 +25,15 @@ declare global {
PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID: string;
PUBLIC_CUSTOMER_ACCOUNT_API_URL: string;
PUBLIC_CHECKOUT_DOMAIN: string;
+ SHOP_ID: string;
+ SENTRY_TRACE?: string;
+ SENTRY_BAGGAGE?: string;
}
}
declare module 'react-router' {
/**
- * Declare local additions to the Remix loader context.
+ * Declare local additions to the React Router loader context.
*/
interface AppLoadContext {
env: Env;
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json
index f564462c7779..01b6c5c1396c 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/package.json
@@ -17,21 +17,20 @@
"@sentry/cloudflare": "latest || *",
"@sentry/react-router": "latest || *",
"@sentry/vite-plugin": "^4.6.2",
- "@shopify/hydrogen": "2025.5.0",
- "@shopify/remix-oxygen": "^3.0.0",
+ "@shopify/hydrogen": "2025.7.3",
"graphql": "^16.10.0",
"graphql-tag": "^2.12.6",
"isbot": "^5.1.22",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-router": "7.9.6",
- "react-router-dom": "7.9.6"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router": "7.12.0",
+ "react-router-dom": "7.12.0"
},
"devDependencies": {
"@graphql-codegen/cli": "5.0.2",
"@playwright/test": "~1.56.0",
- "@react-router/dev": "7.9.6",
- "@react-router/fs-routes": "7.9.6",
+ "@react-router/dev": "7.12.0",
+ "@react-router/fs-routes": "7.12.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@shopify/cli": "3.80.4",
"@shopify/hydrogen-codegen": "^0.3.3",
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts
index b430f97b1f44..65820e5a94c6 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/server.ts
@@ -1,34 +1,11 @@
-import {
- cartGetIdDefault,
- cartSetIdDefault,
- createCartHandler,
- createCustomerAccountClient,
- createStorefrontClient,
- storefrontRedirect,
-} from '@shopify/hydrogen';
-import { type AppLoadContext, createRequestHandler, getStorefrontHeaders } from '@shopify/remix-oxygen';
-import { CART_QUERY_FRAGMENT } from '~/lib/fragments';
-import { AppSession } from '~/lib/session';
-import { wrapRequestHandler } from '@sentry/cloudflare';
-// Virtual entry point for the app
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-expect-error
import * as serverBuild from 'virtual:react-router/server-build';
+import { createRequestHandler, storefrontRedirect } from '@shopify/hydrogen';
+import { createHydrogenRouterContext } from '~/lib/context';
+import { wrapRequestHandler } from '@sentry/cloudflare';
/**
* Export a fetch handler in module format.
*/
-type Env = {
- SESSION_SECRET: string;
- PUBLIC_STOREFRONT_API_TOKEN: string;
- PRIVATE_STOREFRONT_API_TOKEN: string;
- PUBLIC_STORE_DOMAIN: string;
- PUBLIC_STOREFRONT_ID: string;
- PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID: string;
- PUBLIC_CUSTOMER_ACCOUNT_API_URL: string;
- // Add any other environment variables your app expects here
-};
-
export default {
async fetch(request: Request, env: Env, executionContext: ExecutionContext): Promise {
return wrapRequestHandler(
@@ -45,82 +22,35 @@ export default {
},
async () => {
try {
- /**
- * Open a cache instance in the worker and a custom session instance.
- */
- if (!env?.SESSION_SECRET) {
- throw new Error('SESSION_SECRET environment variable is not set');
- }
-
- const waitUntil = executionContext.waitUntil.bind(executionContext);
- const [cache, session] = await Promise.all([
- caches.open('hydrogen'),
- AppSession.init(request, [env.SESSION_SECRET]),
- ]);
+ const hydrogenContext = await createHydrogenRouterContext(request, env, executionContext);
/**
- * Create Hydrogen's Storefront client.
- */
- const { storefront } = createStorefrontClient({
- cache,
- waitUntil,
- i18n: { language: 'EN', country: 'US' },
- publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,
- privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN,
- storeDomain: env.PUBLIC_STORE_DOMAIN,
- storefrontId: env.PUBLIC_STOREFRONT_ID,
- storefrontHeaders: getStorefrontHeaders(request),
- });
-
- /**
- * Create a client for Customer Account API.
- */
- const customerAccount = createCustomerAccountClient({
- waitUntil,
- request,
- session,
- customerAccountId: env.PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID,
- shopId: env.PUBLIC_STORE_DOMAIN,
- });
-
- /*
- * Create a cart handler that will be used to
- * create and update the cart in the session.
- */
- const cart = createCartHandler({
- storefront,
- customerAccount,
- getCartId: cartGetIdDefault(request.headers),
- setCartId: cartSetIdDefault(),
- cartQueryFragment: CART_QUERY_FRAGMENT,
- });
-
- /**
- * Create a Remix request handler and pass
- * Hydrogen's Storefront client to the loader context.
+ * Create a Hydrogen request handler that internally
+ * delegates to React Router for routing and rendering.
*/
const handleRequest = createRequestHandler({
build: serverBuild,
mode: process.env.NODE_ENV,
- getLoadContext: (): AppLoadContext => ({
- session,
- storefront,
- customerAccount,
- cart,
- env,
- waitUntil,
- }),
+ getLoadContext: () => hydrogenContext,
});
const response = await handleRequest(request);
+ if (hydrogenContext.session.isPending) {
+ response.headers.set('Set-Cookie', await hydrogenContext.session.commit());
+ }
+
if (response.status === 404) {
/**
* Check for redirects only when there's a 404 from the app.
* If the redirect doesn't exist, then `storefrontRedirect`
* will pass through the 404 response.
*/
- return storefrontRedirect({ request, response, storefront });
+ return storefrontRedirect({
+ request,
+ response,
+ storefront: hydrogenContext.storefront,
+ });
}
return response;
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/tsconfig.json b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/tsconfig.json
index 6b1b95f76f6f..cbbb69030316 100644
--- a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/tsconfig.json
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/tsconfig.json
@@ -1,5 +1,14 @@
{
- "include": ["server.ts", "./app/**/*.d.ts", "./app/**/*.ts", "./app/**/*.tsx", ".react-router/types/**/*"],
+ "include": [
+ "env.d.ts",
+ "globals.d.ts",
+ "virtual-modules.d.ts",
+ "server.ts",
+ "./app/**/*.d.ts",
+ "./app/**/*.ts",
+ "./app/**/*.tsx",
+ ".react-router/types/**/*"
+ ],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"isolatedModules": true,
diff --git a/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/virtual-modules.d.ts b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/virtual-modules.d.ts
new file mode 100644
index 000000000000..5581f5422868
--- /dev/null
+++ b/dev-packages/e2e-tests/test-applications/hydrogen-react-router-7/virtual-modules.d.ts
@@ -0,0 +1,5 @@
+declare module 'virtual:react-router/server-build' {
+ import type { ServerBuild } from 'react-router';
+ const build: ServerBuild;
+ export = build;
+}