diff --git a/examples/remix/app/components/preview-banner.tsx b/examples/remix/app/components/preview-banner.tsx index 29ef9341..4e6567d2 100644 --- a/examples/remix/app/components/preview-banner.tsx +++ b/examples/remix/app/components/preview-banner.tsx @@ -2,11 +2,11 @@ import React from 'react'; export function PreviewBanner() { return ( -

- You're in preview mode (DRAFT content from Contentful served) + <> +

You're in preview mode (DRAFT content from Contentful served)

-

+ ); } diff --git a/examples/remix/app/routes/$slug.tsx b/examples/remix/app/routes/$slug.tsx index 35b47c9d..bb43538b 100644 --- a/examples/remix/app/routes/$slug.tsx +++ b/examples/remix/app/routes/$slug.tsx @@ -9,27 +9,9 @@ import { } from '@contentful/live-preview/react'; import { PreviewBanner } from '../components/preview-banner'; -import { contentful } from '../../lib/contentful.server'; +import { getEntryBySlug } from '../../lib/contentful.server'; import { isPreviewMode } from '../utils/preview-mode.server'; - -type QueryResponse = { - postCollection: { - items: Post[]; - }; -}; - -type Post = { - title: string; - description: string; - sys: { - id: string; - }; -}; - -type LoaderData = { - post: Post; - preview: boolean; -}; +import type { LoaderData } from '../../types'; const getPostQuery = gql` query Post($slug: String!, $preview: Boolean!) { @@ -49,20 +31,15 @@ const getPostQuery = gql` export const loader: LoaderFunction = async ({ params, request }) => { const { slug } = params; - const preview = await isPreviewMode(request); - - const API_TOKEN = preview - ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN - : process.env.CONTENTFUL_ACCESS_TOKEN; - - const data = (await contentful.request( - getPostQuery, - { slug, preview }, - { authorization: `Bearer ${API_TOKEN}` } - )) as QueryResponse; - - const post = data.postCollection.items[0]; + const data = slug && await getEntryBySlug({ + spaceId: process.env.CONTENTFUL_SPACE_ID || '', + accessToken: preview ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN || '' : process.env.CONTENTFUL_ACCESS_TOKEN || '', + query: getPostQuery, + slug, + preview + }); + const post = data && data.postCollection.items[0]; return json({ post, @@ -72,14 +49,19 @@ export const loader: LoaderFunction = async ({ params, request }) => { export default function PostDetailPage() { const { post, preview } = useLoaderData(); - const inspectorProps = useContentfulInspectorMode({ entryId: post.sys.id }); + const inspectorProps = useContentfulInspectorMode({ entryId: post?.sys.id }); const updatedPost = useContentfulLiveUpdates(post); return ( <> {preview && } -

{updatedPost.title || ''}

-
{updatedPost.description || ''}
+ + {post && ( + <> +

{updatedPost.title || ''}

+
{updatedPost.description || ''}
+ + )} ); } diff --git a/examples/remix/app/routes/api/preview.ts b/examples/remix/app/routes/api/preview.ts index e1d24dc6..5866f461 100644 --- a/examples/remix/app/routes/api/preview.ts +++ b/examples/remix/app/routes/api/preview.ts @@ -2,18 +2,10 @@ import { gql } from 'graphql-request'; import { json, redirect } from '@remix-run/node'; import type { LoaderFunction } from '@remix-run/node'; -import { contentful } from '../../../lib/contentful.server'; +import { getEntryBySlug } from '../../../lib/contentful.server'; import { previewModeCookie } from '../../utils/preview-mode.server'; import { parseCookie } from '../../utils/parse-cookie.server'; -type QueryResponse = { - postCollection: { - items: { - slug: string; - }[]; - }; -}; - const getPostQuery = gql` query Post($slug: String!) { postCollection(where: { slug: $slug }, limit: 1, preview: true) { @@ -35,13 +27,13 @@ export const loader: LoaderFunction = async ({ request }) => { } // Check if the provided `slug` exists - const data = (await contentful.request( - getPostQuery, - { slug }, - { - authorization: `Bearer ${process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN}`, - } - )) as QueryResponse; + const data = await getEntryBySlug({ + spaceId: process.env.CONTENTFUL_SPACE_ID || '', + accessToken: process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN || '', + query: getPostQuery, + slug, + preview: true + }); // If the slug doesn't exist prevent preview from being enabled if (!data.postCollection.items.length) { diff --git a/examples/remix/app/routes/index.tsx b/examples/remix/app/routes/index.tsx index 84a0414f..721fd491 100644 --- a/examples/remix/app/routes/index.tsx +++ b/examples/remix/app/routes/index.tsx @@ -4,26 +4,10 @@ import { json } from '@remix-run/node'; import { useLoaderData } from '@remix-run/react'; import { gql } from 'graphql-request'; -import { contentful } from '../../lib/contentful.server'; +import { getEntries } from '../../lib/contentful.server'; import { isPreviewMode } from '../utils/preview-mode.server'; import { PreviewBanner } from '../components/preview-banner'; - -type QueryResponse = { - postCollection: { - items: Post[]; - }; -}; - -type Post = { - title: string; - description: string; - slug: string; -}; - -type LoaderData = { - posts: Post[]; - preview: boolean; -}; +import type { LoaderData, Post } from '../../types'; const getPostQuery = gql` query Post($preview: Boolean!) { @@ -44,18 +28,12 @@ const getPostQuery = gql` export const loader: LoaderFunction = async ({ request }) => { const preview = await isPreviewMode(request); - - const API_TOKEN = preview - ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN - : process.env.CONTENTFUL_ACCESS_TOKEN; - - const data = (await contentful.request( - getPostQuery, - { preview }, - { - authorization: `Bearer ${API_TOKEN}`, - } - )) as QueryResponse; + const data = await getEntries({ + spaceId: process.env.CONTENTFUL_SPACE_ID || '', + accessToken: preview ? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN || '' : process.env.CONTENTFUL_ACCESS_TOKEN || '', + query: getPostQuery, + preview + }) return json({ posts: data.postCollection.items, @@ -69,7 +47,7 @@ export default function Index() { return ( <> {preview && } - {posts.map((post) => ( + {posts && posts.map((post: Post) => ( {post.title} diff --git a/examples/remix/lib/contentful.server.ts b/examples/remix/lib/contentful.server.ts index 176f241d..8eed37e2 100644 --- a/examples/remix/lib/contentful.server.ts +++ b/examples/remix/lib/contentful.server.ts @@ -1,7 +1,43 @@ import { GraphQLClient } from 'graphql-request'; +import type { QueryResponse, Variables } from '../types' -const SPACE = process.env.CONTENTFUL_SPACE_ID; +const fetchData = async (spaceId: string, accessToken: string, query: string, variables: Variables, preview: boolean) => { + const client = new GraphQLClient(`https://graphql.contentful.com/content/v1/spaces/${spaceId}`); + const data = (await client.request( + query, + variables, + { authorization: `Bearer ${accessToken}` } + )) as QueryResponse; -const endpoint = `https://graphql.contentful.com/content/v1/spaces/${SPACE}`; + return data; +} -export const contentful = new GraphQLClient(endpoint); +// The `spaceId` and `accessToken` are passed as arguments from a `loader` function +// to these utility functions. This approach avoids browser errors that occur when +// trying to access `process.env` from this file, even though it is not executed in +// the browser. This is likely a Remix bundling issue. +export const getEntryBySlug = async ({ + spaceId, + accessToken, + query, + slug, + preview, +}: { + spaceId: string, + accessToken: string, + query: string, + slug: string, + preview: boolean, +}) => fetchData(spaceId, accessToken, query, { slug, preview }, preview); + +export const getEntries = async ({ + spaceId, + accessToken, + query, + preview, +}: { + spaceId: string, + accessToken: string, + query: string, + preview: boolean, +}) => fetchData(spaceId, accessToken, query, { preview }, preview); diff --git a/examples/remix/types.d.ts b/examples/remix/types.d.ts new file mode 100644 index 00000000..573bcd96 --- /dev/null +++ b/examples/remix/types.d.ts @@ -0,0 +1,24 @@ +export type Post = { + title: string; + description: string; + slug: string; + sys: { + id: string; + }; +}; + +export type LoaderData = { + post: Post; + posts: Post[]; + preview: boolean; +}; + +export type QueryResponse = { + postCollection: { + items: Post[]; + }; +}; + +export type Variables = { + [key: string]: string | boolean; +};