-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: explain setup with apollo for CSM graphQL (#763)
* docs: explain setup with apollo for CSM graphQL * docs(content-source-maps): add example for content-source-maps with apollo GraphQL [] * docs(content-source-maps): update tip for apollo/client [] --------- Co-authored-by: Chris Helgert <[email protected]>
- Loading branch information
1 parent
f173594
commit e26c7e0
Showing
14 changed files
with
329 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,8 @@ | ||
# This is the Space ID from your Contentful space. | ||
CONTENTFUL_SPACE_ID= | ||
# This is the Content Delivery API - access token, which is used for fetching published data from your Contentful space. | ||
CONTENTFUL_ACCESS_TOKEN= | ||
# This is the Content Preview API - access token, which is used for fetching draft data from your Contentful space. | ||
CONTENTFUL_PREVIEW_ACCESS_TOKEN= | ||
# This can be any value you want. It must be URL friendly as it will be send as a query parameter to enable draft mode. | ||
CONTENTFUL_PREVIEW_SECRET= |
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,35 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
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,15 @@ | ||
> ⚠️ **This feature is currently in alpha for selected customers.** | ||
# Next.js App Router GraphQL Content Source Maps example | ||
|
||
This is an example project that demonstrates how to use Content Source Maps with a Next.js App Router application. It is using the GraphQL API of Contentful. | ||
|
||
## What? | ||
|
||
[Inspector mode](https://www.contentful.com/developers/docs/tutorials/general/live-preview/) helps to simplify navigation within complex content structures. However, setup costs for complex pages can be time-consuming. To address this, Content Source Maps automates tagging and enables integration with [Vercel’s Content Links](https://vercel.com/docs/workflow-collaboration/edit-mode#content-link) feature, enhancing usability and collaboration. | ||
|
||
## How? | ||
|
||
It uses the GraphQL query level directive `@contentSourceMaps` to generate Content Source Maps for preview content. This content is parsed through the `encodeGraphQLResponse` function from the Live Preview SDK. It returns with the content that includes the hidden metadata to enable live preview inspector mode and Vercel Content Links. | ||
|
||
For more information around Content Source Maps check out the [README](https://github.com/contentful/live-preview/tree/main/packages/content-source-maps) for Content Source Maps. |
6 changes: 6 additions & 0 deletions
6
examples/content-source-maps-apollo/app/api/disable-draft/route.ts
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,6 @@ | ||
import { draftMode } from 'next/headers'; | ||
|
||
export async function GET(request: Request) { | ||
draftMode().disable(); | ||
return new Response('Draft mode is disabled'); | ||
} |
36 changes: 36 additions & 0 deletions
36
examples/content-source-maps-apollo/app/api/enable-draft/route.ts
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,36 @@ | ||
import { cookies, draftMode } from 'next/headers'; | ||
import { redirect } from 'next/navigation'; | ||
|
||
export async function GET(request: Request) { | ||
// Parse query string parameters | ||
const { searchParams } = new URL(request.url); | ||
const secret = searchParams.get('secret'); | ||
const bypass = searchParams.get('x-vercel-protection-bypass'); | ||
|
||
if (!secret) { | ||
return new Response('Missing parameters', { status: 400 }); | ||
} | ||
|
||
// This secret should only be known to this route handler and the CMS | ||
if (secret !== process.env.CONTENTFUL_PREVIEW_SECRET) { | ||
return new Response('Invalid token', { status: 401 }); | ||
} | ||
|
||
// Enable Draft Mode by setting the cookie | ||
draftMode().enable(); | ||
|
||
// Override cookie header for draft mode for usage in live-preview | ||
// https://github.com/vercel/next.js/issues/49927 | ||
const cookieStore = cookies(); | ||
const cookie = cookieStore.get('__prerender_bypass')!; | ||
cookies().set({ | ||
name: '__prerender_bypass', | ||
value: cookie?.value, | ||
httpOnly: true, | ||
path: '/', | ||
secure: true, | ||
sameSite: 'none', | ||
}); | ||
|
||
redirect(`/?x-vercel-protection-bypass=${bypass}&x-vercel-set-bypass-cookie=samesitenone`); | ||
} |
Binary file not shown.
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,28 @@ | ||
import type { Metadata } from 'next'; | ||
import { draftMode } from 'next/headers'; | ||
import { ContentfulPreviewProvider } from '../components/contentful-preview-provider'; | ||
|
||
export const metadata: Metadata = { | ||
title: 'Create Next App', | ||
description: 'Generated by create next app', | ||
}; | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: Readonly<{ | ||
children: React.ReactNode; | ||
}>) { | ||
const { isEnabled } = draftMode(); | ||
|
||
return ( | ||
<ContentfulPreviewProvider | ||
locale="en-US" | ||
enableInspectorMode={isEnabled} | ||
enableLiveUpdates={isEnabled} | ||
> | ||
<html lang="en"> | ||
<body>{children}</body> | ||
</html> | ||
</ContentfulPreviewProvider> | ||
); | ||
} |
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,20 @@ | ||
import { draftMode } from 'next/headers'; | ||
import { getAllPostsForHome } from '../lib/api-graphql'; | ||
|
||
export default async function Home() { | ||
const { isEnabled } = draftMode(); | ||
const posts = await getAllPostsForHome(isEnabled); | ||
|
||
return ( | ||
<main> | ||
<ul> | ||
{posts && | ||
posts.map((post, i) => ( | ||
<li key={i}> | ||
<h1>{post.title}</h1> | ||
</li> | ||
))} | ||
</ul> | ||
</main> | ||
); | ||
} |
12 changes: 12 additions & 0 deletions
12
examples/content-source-maps-apollo/components/contentful-preview-provider.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,12 @@ | ||
'use client'; | ||
|
||
import { ContentfulLivePreviewInitConfig } from '@contentful/live-preview'; | ||
import { ContentfulLivePreviewProvider } from '@contentful/live-preview/react'; | ||
import { PropsWithChildren } from 'react'; | ||
|
||
export function ContentfulPreviewProvider({ | ||
children, | ||
...props | ||
}: PropsWithChildren<ContentfulLivePreviewInitConfig>) { | ||
return <ContentfulLivePreviewProvider {...props}>{children}</ContentfulLivePreviewProvider>; | ||
} |
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,101 @@ | ||
import { ApolloClient, ApolloLink, from, gql, HttpLink, InMemoryCache } from '@apollo/client'; | ||
import { encodeGraphQLResponse } from '@contentful/live-preview'; | ||
|
||
interface Sys { | ||
id: string; | ||
} | ||
|
||
export interface Post { | ||
__typename: string; | ||
sys: Sys; | ||
title: string; | ||
} | ||
|
||
const POST_GRAPHQL_FIELDS = ` | ||
__typename | ||
sys { | ||
id | ||
} | ||
title | ||
`; | ||
|
||
interface PostCollection { | ||
items: Post[]; | ||
} | ||
|
||
interface FetchResponse { | ||
data?: { | ||
postCollection?: PostCollection; | ||
}; | ||
} | ||
|
||
async function fetchGraphQL(query: string, draftMode = false): Promise<any> { | ||
try { | ||
// https://github.com/apollographql/apollo-feature-requests/issues/117 | ||
const client = new ApolloClient({ | ||
cache: new InMemoryCache(), | ||
link: from([ | ||
new ApolloLink((operation, forward) => { | ||
return forward(operation).map((response) => { | ||
response.data = { | ||
...response.data, | ||
extensions: response.extensions, | ||
}; | ||
return response; | ||
}); | ||
}), | ||
new HttpLink({ | ||
uri: `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
Authorization: `Bearer ${ | ||
draftMode | ||
? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN | ||
: process.env.CONTENTFUL_ACCESS_TOKEN | ||
}`, | ||
}, | ||
}), | ||
]), | ||
}); | ||
|
||
const result = await client.query({ | ||
query: gql(query), | ||
// Important: With cache the extensions are stil stripped away | ||
fetchPolicy: draftMode ? 'no-cache' : 'cache-first', | ||
}); | ||
|
||
if (result.errors) { | ||
console.error(result.errors); | ||
throw new Error(`GraphQL query errors: ${result.errors.map((e) => e.message).join(', ')}`); | ||
} | ||
|
||
const { extensions, ...data } = result.data; | ||
|
||
return { data, extensions }; | ||
} catch (error) { | ||
console.error(`Error in fetchGraphQL: ${error}`); | ||
return { errors: [(error as any).message] }; | ||
} | ||
} | ||
|
||
function extractPostEntries(fetchResponse: FetchResponse): Post[] | undefined { | ||
return fetchResponse?.data?.postCollection?.items; | ||
} | ||
|
||
export async function getAllPostsForHome(draftMode: boolean): Promise<Post[] | undefined> { | ||
const entries = await fetchGraphQL( | ||
`query @contentSourceMaps { | ||
postCollection(preview: ${draftMode ? 'true' : 'false'}, limit: 2) { | ||
items { | ||
${POST_GRAPHQL_FIELDS} | ||
} | ||
} | ||
}`, | ||
draftMode, | ||
); | ||
|
||
// Conditionally encode the entries only in draft mode | ||
const finalEntries = draftMode ? encodeGraphQLResponse(entries) : entries; | ||
|
||
return extractPostEntries(finalEntries); | ||
} |
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,19 @@ | ||
module.exports = { | ||
async headers() { | ||
return [ | ||
{ | ||
source: '/:path*', | ||
headers: [ | ||
{ | ||
key: 'X-Frame-Options', | ||
value: 'SAMEORIGIN', | ||
}, | ||
{ | ||
key: 'Content-Security-Policy', | ||
value: `frame-ancestors 'self' https://app.contentful.com http://localhost:3001/`, | ||
}, | ||
], | ||
}, | ||
]; | ||
}, | ||
}; |
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,19 @@ | ||
{ | ||
"private": true, | ||
"scripts": { | ||
"dev": "next dev", | ||
"build": "next build", | ||
"start": "next start" | ||
}, | ||
"dependencies": { | ||
"@contentful/live-preview": "^4.2.2", | ||
"@types/node": "20.2.3", | ||
"@types/react": "18.2.7", | ||
"@types/react-dom": "18.2.4", | ||
"@apollo/client": "3.11.8", | ||
"next": "14.2.10", | ||
"react": "18.2.0", | ||
"react-dom": "18.2.0", | ||
"typescript": "5.0.4" | ||
} | ||
} |
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,28 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es5", | ||
"lib": ["dom", "dom.iterable", "esnext"], | ||
"allowJs": true, | ||
"skipLibCheck": true, | ||
"strict": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"noEmit": true, | ||
"esModuleInterop": true, | ||
"module": "esnext", | ||
"moduleResolution": "node", | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"jsx": "preserve", | ||
"incremental": true, | ||
"plugins": [ | ||
{ | ||
"name": "next" | ||
} | ||
], | ||
"paths": { | ||
"@/*": ["./src/*"] | ||
} | ||
}, | ||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], | ||
"exclude": ["node_modules"] | ||
} |
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