-
Notifications
You must be signed in to change notification settings - Fork 287
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* example third-party queries and caching * add example to turbo
- Loading branch information
1 parent
3474c0e
commit 081b41e
Showing
22 changed files
with
1,610 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
build | ||
node_modules | ||
bin | ||
*.d.ts | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
* @type {import("@types/eslint").Linter.BaseConfig} | ||
*/ | ||
module.exports = { | ||
extends: [ | ||
'@remix-run/eslint-config', | ||
'plugin:hydrogen/recommended', | ||
'plugin:hydrogen/typescript', | ||
], | ||
rules: { | ||
'@typescript-eslint/ban-ts-comment': 'off', | ||
'@typescript-eslint/naming-convention': 'off', | ||
'hydrogen/prefer-image-component': 'off', | ||
'no-useless-escape': 'off', | ||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off', | ||
'no-case-declarations': 'off', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
node_modules | ||
/.cache | ||
/build | ||
/dist | ||
/public/build | ||
/.mf | ||
.env | ||
.shopify |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
schema: node_modules/@shopify/hydrogen-react/storefront.schema.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# Hydrogen example: Third-party Queries and Caching | ||
|
||
This folder contains shows how to leverage Oxygen's sub-request caching when querying | ||
third-party GraphQL API in Hydrogen. This example uses the public [Rick & Morty API](https://rickandmortyapi.com/documentation/#graphql) | ||
|
||
<img width="981" alt="Screenshot 2023-11-13 at 3 51 32 PM" src="https://github.com/juanpprieto/hydrogen-third-party-api/assets/12080141/fe648c70-a979-4862-a173-4c0244543dec"> | ||
|
||
## Requirements | ||
|
||
- Basic knowledge of GraphQL and the [Rick & Morty API](https://rickandmortyapi.com/documentation/#graphql) | ||
|
||
## Key files | ||
|
||
This folder contains the minimal set of files needed to showcase the implementation. | ||
Files that aren’t included by default with Hydrogen and that you’ll need to | ||
create are labeled with 🆕. | ||
|
||
| File | Description | | ||
| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | ||
| 🆕 [`app/utils/createRickAndMortyClient.server.ts`](app/utils/createRickAndMortyClient.server.ts) | Rick & Morty GraphQL client factory function with Oxygen caching | | ||
| [`server.ts`](server.ts) | Oxygen server worker | | ||
| [`remix.env.d.ts`](remix.env.d.ts) | (Optional) Oxygen/Hydrogen TypeScript types | | ||
| [`app/routes/_index.tsx`](app/routes/_index.tsx) | Hydrogen homepage route | | ||
|
||
## Instructions | ||
|
||
### 1. Connect to your store to link the required environment variables | ||
|
||
```bash | ||
h2 link | ||
``` | ||
|
||
### 2. Copy over the new file `createRickAndMortyClient.server.ts` to `app/utils/` | ||
|
||
### 3. Edit the worker file `server.ts` | ||
|
||
import `createRickAndMortyClient`, create a client instance and pass it to the `getLoadedContext`. | ||
|
||
```ts | ||
import {createRickAndMortyClient} from './app/utils/createRickAndMortyClient.server'; | ||
// ...other imports | ||
|
||
export default { | ||
async fetch( | ||
request: Request, | ||
env: Env, | ||
executionContext: ExecutionContext, | ||
): Promise<Response> { | ||
try { | ||
// ...other code | ||
|
||
/** | ||
* Create a Rick and Morty client. | ||
*/ | ||
const rickAndMorty = createRickAndMortyClient({ cache, waitUntil }); | ||
|
||
/** | ||
* Create a Remix request handler and pass | ||
* Hydrogen's Storefront client to the loader context. | ||
*/ | ||
const handleRequest = createRequestHandler({ | ||
build: remixBuild, | ||
mode: process.env.NODE_ENV, | ||
getLoadContext: () => ({ | ||
// ...other code | ||
rickAndMorty, // Pass the Rick and Morty client to the action and loader context. | ||
}), | ||
}); | ||
|
||
// ...other code | ||
} catch {} | ||
}; | ||
``` | ||
[View the complete server.ts file](app/server.ts) to see these updates in context. | ||
If using TypeScript you will also need to update `remix.en.d.ts`. Import `createRickAndMortyClient` | ||
and add the `rickAndMorty` property to the `AppLoadContext` interface. | ||
```ts | ||
// ...other code | ||
import {createRickAndMortyClient} from './app/utils/createRickAndMortyClient.server'; | ||
|
||
// ...other code | ||
|
||
declare module '@shopify/remix-oxygen' { | ||
/** | ||
* Declare local additions to the Remix loader context. | ||
*/ | ||
export interface AppLoadContext { | ||
// ...other code | ||
rickAndMorty: ReturnType<typeof createRickAndMortyClient>; | ||
} | ||
``` | ||
|
||
[View the complete remix.d.ts file](remix.d.ts) to see these updates in context. | ||
|
||
## 4. Query the Rick & Morty API on the home route `/app/routes/_index.tsx` | ||
|
||
Add the query to fetch Rick & Morty characters | ||
|
||
```ts | ||
const CHARACTERS_QUERY = `#graphql:rickAndMorty | ||
query { | ||
characters(page: 1) { | ||
results { | ||
name | ||
id | ||
} | ||
} | ||
} | ||
`; | ||
``` | ||
|
||
Query the Rick & Morty API inisde the `loader` function | ||
|
||
```ts | ||
export async function loader({context}: LoaderFunctionArgs) { | ||
const {characters} = await context.rickAndMorty.query(CHARACTERS_QUERY, { | ||
cache: CacheShort(), | ||
}); | ||
return json({characters}); | ||
} | ||
``` | ||
|
||
Render the characters list in the homepage | ||
|
||
```ts | ||
type Character = { | ||
name: string; | ||
id: string; | ||
}; | ||
|
||
export default function Homepage() { | ||
const {characters} = useLoaderData<typeof loader>(); | ||
return ( | ||
<div> | ||
<h1>Rick & Morty Characters</h1> | ||
{/* 2. Render data from the Rick & Morty GraphQL API: */} | ||
<ul> | ||
{(characters.results || []).map((character: Character) => ( | ||
<li key={character.name}>{character.name}</li> | ||
))} | ||
</ul> | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
[View the complete remix.d.ts file](/app/routes/_index.tsx) to see these updates in context. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {RemixBrowser} from '@remix-run/react'; | ||
import {startTransition, StrictMode} from 'react'; | ||
import {hydrateRoot} from 'react-dom/client'; | ||
|
||
startTransition(() => { | ||
hydrateRoot( | ||
document, | ||
<StrictMode> | ||
<RemixBrowser /> | ||
</StrictMode>, | ||
); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import type {EntryContext} from '@shopify/remix-oxygen'; | ||
import {RemixServer} from '@remix-run/react'; | ||
import isbot from 'isbot'; | ||
import {renderToReadableStream} from 'react-dom/server'; | ||
import {createContentSecurityPolicy} from '@shopify/hydrogen'; | ||
|
||
export default async function handleRequest( | ||
request: Request, | ||
responseStatusCode: number, | ||
responseHeaders: Headers, | ||
remixContext: EntryContext, | ||
) { | ||
const {nonce, header, NonceProvider} = createContentSecurityPolicy(); | ||
|
||
const body = await renderToReadableStream( | ||
<NonceProvider> | ||
<RemixServer context={remixContext} url={request.url} /> | ||
</NonceProvider>, | ||
{ | ||
nonce, | ||
signal: request.signal, | ||
onError(error) { | ||
// eslint-disable-next-line no-console | ||
console.error(error); | ||
responseStatusCode = 500; | ||
}, | ||
}, | ||
); | ||
|
||
if (isbot(request.headers.get('user-agent'))) { | ||
await body.allReady; | ||
} | ||
|
||
responseHeaders.set('Content-Type', 'text/html'); | ||
responseHeaders.set('Content-Security-Policy', header); | ||
|
||
return new Response(body, { | ||
headers: responseHeaders, | ||
status: responseStatusCode, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import {useNonce} from '@shopify/hydrogen'; | ||
import { | ||
Links, | ||
Meta, | ||
Outlet, | ||
Scripts, | ||
LiveReload, | ||
ScrollRestoration, | ||
} from '@remix-run/react'; | ||
import favicon from '../public/favicon.svg'; | ||
import resetStyles from './styles/reset.css'; | ||
import appStyles from './styles/app.css'; | ||
|
||
export function links() { | ||
return [ | ||
{rel: 'stylesheet', href: resetStyles}, | ||
{rel: 'stylesheet', href: appStyles}, | ||
{ | ||
rel: 'preconnect', | ||
href: 'https://cdn.shopify.com', | ||
}, | ||
{ | ||
rel: 'preconnect', | ||
href: 'https://shop.app', | ||
}, | ||
{rel: 'icon', type: 'image/svg+xml', href: favicon}, | ||
]; | ||
} | ||
|
||
export default function App() { | ||
const nonce = useNonce(); | ||
|
||
return ( | ||
<html lang="en"> | ||
<head> | ||
<meta charSet="utf-8" /> | ||
<meta name="viewport" content="width=device-width,initial-scale=1" /> | ||
<Meta /> | ||
<Links /> | ||
</head> | ||
<body style={{padding: '2rem'}}> | ||
<Outlet /> | ||
<ScrollRestoration nonce={nonce} /> | ||
<Scripts nonce={nonce} /> | ||
<LiveReload nonce={nonce} /> | ||
</body> | ||
</html> | ||
); | ||
} |
44 changes: 44 additions & 0 deletions
44
examples/third-party-queries-caching/app/routes/_index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; | ||
import {useLoaderData} from '@remix-run/react'; | ||
import {CacheShort} from '@shopify/hydrogen'; | ||
|
||
export async function loader({context}: LoaderFunctionArgs) { | ||
// 1. Fetch characters from the Rick & Morty GraphQL API | ||
const {characters} = await context.rickAndMorty.query(CHARACTERS_QUERY, { | ||
cache: CacheShort(), | ||
}); | ||
return json({characters}); | ||
} | ||
|
||
type Character = { | ||
name: string; | ||
id: string; | ||
}; | ||
|
||
export default function Homepage() { | ||
const {characters} = useLoaderData<typeof loader>(); | ||
return ( | ||
<div> | ||
<h1>Rick & Morty Characters</h1> | ||
{/* 2. Render data from the Rick & Morty GraphQL API: */} | ||
<ul> | ||
{(characters.results || []).map((character: Character) => ( | ||
<li key={character.name}>{character.name}</li> | ||
))} | ||
</ul> | ||
</div> | ||
); | ||
} | ||
|
||
// 3. The Rick & Morty characters GraphQL query | ||
// NOTE: https://rickandmortyapi.com/documentation/#graphql | ||
const CHARACTERS_QUERY = `#graphql:rickAndMorty | ||
query { | ||
characters(page: 1) { | ||
results { | ||
name | ||
id | ||
} | ||
} | ||
} | ||
`; |
Oops, something went wrong.