11import { useNonce } from '@shopify/hydrogen' ;
2- import {
3- defer ,
4- type SerializeFrom ,
5- type LoaderFunctionArgs ,
6- } from '@shopify/remix-oxygen' ;
2+ import { defer , type LoaderFunctionArgs } from '@shopify/remix-oxygen' ;
73import {
84 Links ,
95 Meta ,
106 Outlet ,
117 Scripts ,
12- useMatches ,
8+ useRouteLoaderData ,
139 useRouteError ,
1410 useLoaderData ,
1511 ScrollRestoration ,
1612 isRouteErrorResponse ,
1713 type ShouldRevalidateFunction ,
1814} from '@remix-run/react' ;
1915import type { CustomerAccessToken } from '@shopify/hydrogen/storefront-api-types' ;
16+ import type {
17+ CartApiQueryFragment ,
18+ FooterQuery ,
19+ HeaderQuery ,
20+ RootMetaObjectQuery ,
21+ } from 'storefrontapi.generated' ;
2022import favicon from '../public/favicon.svg' ;
21- import resetStyles from './styles/reset.css' ;
22- import appStyles from './styles/app.css' ;
23+ import './styles/reset.css' ;
24+ import './styles/app.css' ;
25+
2326import { Layout } from '~/components/Layout' ;
2427
2528/**
@@ -45,8 +48,8 @@ export const shouldRevalidate: ShouldRevalidateFunction = ({
4548
4649export function links ( ) {
4750 return [
48- { rel : 'stylesheet' , href : resetStyles } ,
49- { rel : 'stylesheet' , href : appStyles } ,
51+ { rel : 'stylesheet' , href : '/app/styles/reset.css' } ,
52+ { rel : 'stylesheet' , href : '/app/styles/app.css' } ,
5053 {
5154 rel : 'preconnect' ,
5255 href : 'https://cdn.shopify.com' ,
@@ -55,13 +58,34 @@ export function links() {
5558 rel : 'preconnect' ,
5659 href : 'https://shop.app' ,
5760 } ,
58- { rel : 'icon' , type : 'image/svg+xml' , href : favicon } ,
61+ { rel : 'icon' , type : 'image/svg+xml' , href : favicon } ,
5962 ] ;
6063}
6164
65+
66+ /**
67+ * Remix `SerializeFrom` loses top-level keys on `defer()` payloads; this matches runtime loader data.
68+ */
69+ export type RootLoaderClientData = {
70+ cart : Promise < CartApiQueryFragment | null > ;
71+ footer : Promise < FooterQuery > ;
72+ header : HeaderQuery ;
73+ isLoggedIn : boolean ;
74+ publicStoreDomain : string ;
75+ footerMetaObject : RootMetaObjectQuery ;
76+ } ;
77+
78+ const FALLBACK_HEADER : HeaderQuery = {
79+ shop : {
80+ id : 'gid://shopify/Shop/0' ,
81+ name : '' ,
82+ description : '' ,
83+ primaryDomain : { url : '' } ,
84+ } ,
85+ } ;
86+
6287export const useRootLoaderData = ( ) => {
63- const [ root ] = useMatches ( ) ;
64- return root ?. data as SerializeFrom < typeof loader > ;
88+ return useRouteLoaderData ( 'root' ) as unknown as RootLoaderClientData | undefined ;
6589} ;
6690
6791export async function loader ( { context} : LoaderFunctionArgs ) {
@@ -110,7 +134,7 @@ export async function loader({context}: LoaderFunctionArgs) {
110134
111135export default function App ( ) {
112136 const nonce = useNonce ( ) ;
113- const data = useLoaderData < typeof loader > ( ) ;
137+ const data = useLoaderData < typeof loader > ( ) as unknown as RootLoaderClientData ;
114138 return (
115139 < html lang = "en" >
116140 < head >
@@ -120,7 +144,13 @@ export default function App() {
120144 < Links />
121145 </ head >
122146 < body >
123- < Layout footerMetaObject = { data ?. footerMetaObject } { ...data } >
147+ < Layout
148+ cart = { data . cart }
149+ footer = { data . footer }
150+ header = { data . header }
151+ isLoggedIn = { data . isLoggedIn }
152+ footerMetaObject = { data . footerMetaObject }
153+ >
124154 < Outlet />
125155 </ Layout >
126156 < ScrollRestoration nonce = { nonce } />
@@ -153,7 +183,14 @@ export function ErrorBoundary() {
153183 < Links />
154184 </ head >
155185 < body >
156- < Layout { ...rootData } >
186+ < Layout
187+ fetchData = { rootData }
188+ cart = { rootData ?. cart ?? Promise . resolve ( null ) }
189+ footer = { rootData ?. footer ?? Promise . resolve ( { } as FooterQuery ) }
190+ header = { rootData ?. header ?? FALLBACK_HEADER }
191+ isLoggedIn = { rootData ?. isLoggedIn ?? false }
192+ footerMetaObject = { rootData ?. footerMetaObject }
193+ >
157194 < div className = "route-error" >
158195 < h1 > Oops</ h1 >
159196 < h2 > { errorStatus } </ h2 >
@@ -278,7 +315,7 @@ const FOOTER_QUERY = `#graphql
278315` as const ;
279316
280317const FOOTER_META_OBJECT_QUERY = `#graphql
281- query MetaObject ($country: CountryCode, $language: LanguageCode)
318+ query RootMetaObject ($country: CountryCode, $language: LanguageCode)
282319@inContext(country: $country, language: $language) {
283320 metaobjects(first: 100, type: "footer") {
284321 nodes {
0 commit comments