Skip to content

Commit 3bc6ff5

Browse files
committed
Add remix-hydrogen test app.
1 parent fd3df68 commit 3bc6ff5

27 files changed

+1029
-0
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,7 @@ jobs:
873873
'node-experimental-fastify-app',
874874
'remix-cloudflare-workers',
875875
'remix-cloudflare-pages',
876+
'remix-hydrogen',
876877
]
877878
build-command:
878879
- false
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
build
2+
node_modules
3+
bin
4+
*.d.ts
5+
dist
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* @type {import("@types/eslint").Linter.BaseConfig}
3+
*/
4+
module.exports = {
5+
extends: ['plugin:hydrogen/recommended', 'plugin:hydrogen/typescript'],
6+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules
2+
/.cache
3+
/build
4+
/dist
5+
/public/build
6+
/.mf
7+
.env
8+
.shopify
9+
env.ts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
schema: node_modules/@shopify/hydrogen-react/storefront.schema.json
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@shopify:registry=https://registry.npmjs.com
2+
progress=false
3+
@sentry:registry=http://localhost:4873
4+
@sentry-internal:registry=http://localhost:4873
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Hydrogen template: Hello World
2+
3+
Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen.
4+
5+
[Check out Hydrogen docs](https://shopify.dev/custom-storefronts/hydrogen)
6+
[Get familiar with Remix](https://remix.run/docs/en/v1)
7+
8+
## What's included
9+
10+
- Remix
11+
- Hydrogen
12+
- Oxygen
13+
- Shopify CLI
14+
- ESLint
15+
- Prettier
16+
- GraphQL generator
17+
- TypeScript and JavaScript flavors
18+
- Minimal setup of components and routes
19+
20+
## Getting started
21+
22+
**Requirements:**
23+
24+
- Node.js version 16.14.0 or higher
25+
26+
```bash
27+
npm create @shopify/hydrogen@latest -- --template hello-world
28+
```
29+
30+
Remember to update `.env` with your shop's domain and Storefront API token!
31+
32+
## Building for production
33+
34+
```bash
35+
npm run build
36+
```
37+
38+
## Local development
39+
40+
```bash
41+
npm run dev
42+
```
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {RemixBrowser, useLocation, useMatches} from '@remix-run/react';
2+
import {startTransition, StrictMode, useEffect} from 'react';
3+
import {hydrateRoot} from 'react-dom/client';
4+
import * as Sentry from '@sentry/remix';
5+
import {env} from '../env';
6+
7+
Sentry.init({
8+
dsn: env.SENTRY_DSN,
9+
integrations: [
10+
new Sentry.BrowserTracing({
11+
routingInstrumentation: Sentry.remixRouterInstrumentation(
12+
useEffect,
13+
useLocation,
14+
useMatches,
15+
),
16+
}),
17+
new Sentry.Replay(),
18+
],
19+
// Performance Monitoring
20+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
21+
// Session Replay
22+
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
23+
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
24+
debug: true,
25+
});
26+
27+
Sentry.addGlobalEventProcessor((event) => {
28+
if (
29+
event.type === 'transaction' &&
30+
(event.contexts?.trace?.op === 'pageload' ||
31+
event.contexts?.trace?.op === 'navigation')
32+
) {
33+
const eventId = event.event_id;
34+
if (eventId) {
35+
window.recordedTransactions = window.recordedTransactions || [];
36+
window.recordedTransactions.push(eventId);
37+
}
38+
}
39+
40+
return event;
41+
});
42+
startTransition(() => {
43+
hydrateRoot(
44+
document,
45+
<StrictMode>
46+
<RemixBrowser />
47+
</StrictMode>,
48+
);
49+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type {DataFunctionArgs, EntryContext} from '@shopify/remix-oxygen';
2+
import {RemixServer} from '@remix-run/react';
3+
import isbot from 'isbot';
4+
import {renderToReadableStream} from 'react-dom/server';
5+
import {createContentSecurityPolicy} from '@shopify/hydrogen';
6+
import * as Sentry from '@sentry/remix';
7+
import {env} from '../env';
8+
9+
Sentry.workerInit({
10+
dsn: env.SENTRY_DSN,
11+
// Performance Monitoring
12+
tracesSampleRate: 1.0, // Capture 100% of the transactions, reduce in production!
13+
});
14+
15+
export function handleError(error: unknown, {request}: DataFunctionArgs): void {
16+
Sentry.captureRemixServerException(error, 'remix.server', request);
17+
}
18+
19+
export default async function handleRequest(
20+
request: Request,
21+
responseStatusCode: number,
22+
responseHeaders: Headers,
23+
remixContext: EntryContext,
24+
) {
25+
const {nonce, header, NonceProvider} = createContentSecurityPolicy();
26+
27+
const body = await renderToReadableStream(
28+
<NonceProvider>
29+
<RemixServer context={remixContext} url={request.url} />
30+
</NonceProvider>,
31+
{
32+
nonce,
33+
signal: request.signal,
34+
onError(error) {
35+
// eslint-disable-next-line no-console
36+
console.error(error);
37+
responseStatusCode = 500;
38+
},
39+
},
40+
);
41+
42+
if (isbot(request.headers.get('user-agent'))) {
43+
await body.allReady;
44+
}
45+
46+
responseHeaders.set('Content-Type', 'text/html');
47+
responseHeaders.set('Content-Security-Policy', header);
48+
return new Response(body, {
49+
headers: responseHeaders,
50+
status: responseStatusCode,
51+
});
52+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {type LinksFunction, type LoaderArgs} from '@shopify/remix-oxygen';
2+
import {
3+
Links,
4+
Meta,
5+
Outlet,
6+
Scripts,
7+
LiveReload,
8+
ScrollRestoration,
9+
useLoaderData,
10+
type ShouldRevalidateFunction,
11+
useRouteError,
12+
} from '@remix-run/react';
13+
import type {Shop} from '@shopify/hydrogen/storefront-api-types';
14+
import appStyles from './styles/app.css';
15+
import favicon from '../public/favicon.svg';
16+
import {useNonce} from '@shopify/hydrogen';
17+
import * as Sentry from '@sentry/remix';
18+
19+
// This is important to avoid re-fetching root queries on sub-navigations
20+
export const shouldRevalidate: ShouldRevalidateFunction = ({
21+
formMethod,
22+
currentUrl,
23+
nextUrl,
24+
}) => {
25+
// revalidate when a mutation is performed e.g add to cart, login...
26+
if (formMethod && formMethod !== 'GET') {
27+
return true;
28+
}
29+
30+
// revalidate when manually revalidating via useRevalidator
31+
if (currentUrl.toString() === nextUrl.toString()) {
32+
return true;
33+
}
34+
35+
return false;
36+
};
37+
38+
export const links: LinksFunction = () => {
39+
return [
40+
{rel: 'stylesheet', href: appStyles},
41+
{
42+
rel: 'preconnect',
43+
href: 'https://cdn.shopify.com',
44+
},
45+
{
46+
rel: 'preconnect',
47+
href: 'https://shop.app',
48+
},
49+
{rel: 'icon', type: 'image/svg+xml', href: favicon},
50+
];
51+
};
52+
53+
export async function loader({context}: LoaderArgs) {
54+
const layout = await context.storefront.query<{shop: Shop}>(LAYOUT_QUERY);
55+
return {
56+
layout,
57+
};
58+
}
59+
60+
export function ErrorBoundary() {
61+
const error = useRouteError();
62+
const eventId = Sentry.captureRemixErrorBoundaryError(error);
63+
64+
return (
65+
<div>
66+
<span>ErrorBoundary Error</span>
67+
<span id="event-id">{eventId}</span>
68+
</div>
69+
);
70+
}
71+
72+
function App() {
73+
const nonce = useNonce();
74+
const data = useLoaderData<typeof loader>();
75+
76+
const {name} = data.layout.shop;
77+
78+
return (
79+
<html lang="en">
80+
<head>
81+
<meta charSet="utf-8" />
82+
<meta name="viewport" content="width=device-width,initial-scale=1" />
83+
<Meta />
84+
<Links />
85+
</head>
86+
<body>
87+
<h1>Hello, {name}</h1>
88+
<p>This is a custom storefront powered by Hydrogen</p>
89+
<Outlet />
90+
<ScrollRestoration nonce={nonce} />
91+
<Scripts nonce={nonce} />
92+
<LiveReload nonce={nonce} />
93+
</body>
94+
</html>
95+
);
96+
}
97+
98+
export default Sentry.withSentry(App);
99+
100+
const LAYOUT_QUERY = `#graphql
101+
query layout {
102+
shop {
103+
name
104+
description
105+
}
106+
}
107+
`;

0 commit comments

Comments
 (0)