added graphcms (#14026)
* added graphcms * Updated readme and environment variables * Removed gitignore * Updated tailwind config * Some fixes in pages * Updated api endpoints * lint fix * Updated readme * Updated og image * Updated cms examples to include this one * Added example to docs * Added preview demo link * Updated step Co-authored-by: Luis Alvarez <luis@vercel.com>
|
@ -20,6 +20,7 @@ description: Next.js has the preview mode for statically generated pages. You ca
|
|||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-cosmic">Cosmic Example</a> (<a href="https://next-blog-cosmic.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-buttercms">ButterCMS Example</a> (<a href="https://next-blog-buttercms.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-storyblok">Storyblok Example</a> (<a href="https://next-blog-storyblok.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-graphcms">GraphCMS Example</a> (<a href="https://next-blog-graphcms.now.sh/">Demo</a>)</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ description: 'Next.js has 2 pre-rendering modes: Static Generation and Server-si
|
|||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-cosmic">Cosmic Example</a> (<a href="https://next-blog-cosmic.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-buttercms">ButterCMS Example</a> (<a href="https://next-blog-buttercms.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-storyblok">Storyblok Example</a> (<a href="https://next-blog-storyblok.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-graphcms">GraphCMS Example</a> (<a href="https://next-blog-graphcms.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://static-tweet.now.sh/">Static Tweet Demo</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
|
|
@ -60,6 +60,7 @@ Finally, you can always use **Client-side Rendering** along with Static Generati
|
|||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-cosmic">Cosmic Example</a> (<a href="https://next-blog-cosmic.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-buttercms">ButterCMS Example</a> (<a href="https://next-blog-buttercms.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-storyblok">Storyblok Example</a> (<a href="https://next-blog-storyblok.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-graphcms">GraphCMS Example</a> (<a href="https://next-blog-graphcms.now.sh/">Demo</a>)</li>
|
||||
<li><a href="https://static-tweet.now.sh/">Static Tweet Demo</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
|
|
@ -27,6 +27,9 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu
|
|||
- [Strapi](/examples/cms-strapi)
|
||||
- [Agility CMS](/examples/cms-agilitycms)
|
||||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
|
||||
## How to use
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ Once you have access to [the environment variables you'll need](#step-15-set-up-
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-2-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [Strapi](/examples/cms-strapi)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -23,6 +23,8 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
|
|||
- [Strapi](/examples/cms-strapi)
|
||||
- [Agility CMS](/examples/cms-agilitycms)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -18,6 +18,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## Deploy your own
|
||||
|
|
4
examples/cms-graphcms/.env.local.example
Normal file
|
@ -0,0 +1,4 @@
|
|||
GRAPHCMS_PROJECT_API=
|
||||
GRAPHCMS_PROD_AUTH_TOKEN=
|
||||
GRAPHCMS_DEV_AUTH_TOKEN=
|
||||
GRAPHCMS_PREVIEW_SECRET=
|
122
examples/cms-graphcms/README.md
Normal file
|
@ -0,0 +1,122 @@
|
|||
# A statically generated blog example using Next.js and GraphCMS
|
||||
|
||||
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [GraphCMS](https://www.graphcms.com/) as the data source.
|
||||
|
||||
## Demo
|
||||
|
||||
- **Live**: [https://next-blog-graphcms.now.sh/](https://next-blog-graphcms.now.sh/)
|
||||
- **Preview Mode**: [https://next-blog-graphcms.now.sh/api/preview...](https://next-blog-graphcms.now.sh/api/preview?secret=PHHsT9YmZjHxjxuzt8Jie2XkieME&slug=technical-seo-with-graphcms)
|
||||
|
||||
### [https://next-blog-graphcms.now.sh/](https://next-blog-graphcms.now.sh/)
|
||||
|
||||
### Related examples
|
||||
|
||||
- [WordPress](/examples/cms-wordpress)
|
||||
- [DatoCMS](/examples/cms-datocms)
|
||||
- [Sanity](/examples/cms-sanity)
|
||||
- [TakeShape](/examples/cms-takeshape)
|
||||
- [Prismic](/examples/cms-prismic)
|
||||
- [Contentful](/examples/cms-contentful)
|
||||
- [Strapi](/examples/cms-strapi)
|
||||
- [Agility CMS](/examples/cms-agilitycms)
|
||||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Once you have access to [the environment variables you'll need](#step-3-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?s=https://github.com/vercel/next.js/tree/canary/examples/cms-graphcms&env=GRAPHCMS_PROJECT_API,GRAPHCMS_PROD_AUTH_TOKEN,GRAPHCMS_DEV_AUTH_TOKEN,GRAPHCMS_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20GraphCMS&envLink=https://vercel.link/cms-graphcms-env)
|
||||
|
||||
## How to use
|
||||
|
||||
### Using `create-next-app`
|
||||
|
||||
Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npm init next-app --example cms-graphcms cms-graphcms-app
|
||||
# or
|
||||
yarn create next-app --example cms-graphcms cms-graphcms-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
||||
Download the example:
|
||||
|
||||
```bash
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/cms-graphcms
|
||||
cd cms-graphcms
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Step 1. Create an account and a project in GraphCMS
|
||||
|
||||
First, [create an account in GraphCMS](https://app.graphcms.com).
|
||||
|
||||
### Step 2. Create a new GraphCMS project
|
||||
|
||||
After creating an account, create a new project from the **Blog Starter template** - be sure to include the example content.
|
||||
|
||||
### Step 3. Set up environment variables
|
||||
|
||||
Copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
|
||||
|
||||
```bash
|
||||
cp .env.local.example .env.local
|
||||
```
|
||||
|
||||
Inside your project dashboard, navigate to **Settings > API Access page**.
|
||||
|
||||
Then set each variable in `.env.local`:
|
||||
|
||||
- `GRAPHCMS_PROJECT_API`: Set it to the API endpoint under **Endpoints**, at the top of the page.
|
||||
- `GRAPHCMS_PROD_AUTH_TOKEN`: Copy the `NEXT_EXAMPLE_CMS_GCMS_PROD_AUTH_TOKEN` token under **Existing tokens**, at the bottom of the page. This will only query content that is published.
|
||||
- `GRAPHCMS_DEV_AUTH_TOKEN`: Copy the `NEXT_EXAMPLE_CMS_GCMS_DEV_AUTH_TOKEN` token under **Existing tokens**, at the bottom of the page. This will only query content that is in draft.
|
||||
- `GRAPHCMS_PREVIEW_SECRET` can be any random string (but avoid spaces), like `MY_SECRET` - this is used for [Preview Mode](https://nextjs.org/docs/advanced-features/preview-mode).
|
||||
|
||||
### Step 4. Run Next.js in development mode
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
|
||||
# or
|
||||
|
||||
yarn install
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/zeit/next.js/discussions).
|
||||
|
||||
### Step 5. Try preview mode
|
||||
|
||||
In GraphCMS, go to one of the posts in your project and:
|
||||
|
||||
- **Update the title**. For example, you can add `[Draft]` in front of the title.
|
||||
- After you edit the document save the article as a draft, but **DO NOT** click **Publish**. By doing this, the post will be in the draft stage.
|
||||
|
||||
Now, if you go to the post page on localhost, you won't see the updated title. However, if you use **Preview Mode**, you'll be able to see the change ([Documentation](/docs/advanced-features/preview-mode.md)).
|
||||
|
||||
To view the preview, transform the url to the following format: `http://localhost:3000/api/preview?secret=<YOUR_SECRET_TOKEN>&slug=<SLUG_TO_PREVIEW>` where `<YOUR_SECRET_TOKEN>` is the same secret you defined in the `.env.local` file and `<SLUG_TO_PREVIEW>` is the slug of one of the posts you want to preview.
|
||||
|
||||
You should now be able to see the updated title. To exit the preview mode, you can click on _"Click here to exit preview mode"_ at the top.
|
||||
|
||||
### Step 6. Deploy on Vercel
|
||||
|
||||
You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||
|
||||
#### Deploy Your Local Project
|
||||
|
||||
To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example).
|
||||
|
||||
**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
|
||||
|
||||
#### Deploy from Our Template
|
||||
|
||||
Alternatively, you can deploy using our template by clicking on the Deploy button below.
|
||||
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?s=https://github.com/vercel/next.js/tree/canary/examples/cms-graphcms&env=GRAPHCMS_PROJECT_API,GRAPHCMS_PROD_AUTH_TOKEN,GRAPHCMS_DEV_AUTH_TOKEN,GRAPHCMS_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20GraphCMS&envLink=https://vercel.link/cms-graphcms-env)
|
42
examples/cms-graphcms/components/alert.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import Container from './container'
|
||||
import cn from 'classnames'
|
||||
import { EXAMPLE_PATH } from '../lib/constants'
|
||||
|
||||
export default function Alert({ preview }) {
|
||||
return (
|
||||
<div
|
||||
className={cn('border-b', {
|
||||
'bg-accent-7 border-accent-7 text-white': preview,
|
||||
'bg-accent-1 border-accent-2': !preview,
|
||||
})}
|
||||
>
|
||||
<Container>
|
||||
<div className="py-2 text-center text-sm">
|
||||
{preview ? (
|
||||
<>
|
||||
This page is a preview.{' '}
|
||||
<a
|
||||
href="/api/exit-preview"
|
||||
className="underline hover:text-cyan duration-200 transition-colors"
|
||||
>
|
||||
Click here
|
||||
</a>{' '}
|
||||
to exit preview mode.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
The source code for this blog is{' '}
|
||||
<a
|
||||
href={`https://github.com/zeit/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
|
||||
className="underline hover:text-success duration-200 transition-colors"
|
||||
>
|
||||
available on GitHub
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
8
examples/cms-graphcms/components/avatar.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default function Avatar({ name, picture }) {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<img src={picture} className="w-12 h-12 rounded-full mr-4" alt={name} />
|
||||
<div className="text-xl font-bold">{name}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
3
examples/cms-graphcms/components/container.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Container({ children }) {
|
||||
return <div className="container mx-auto px-5">{children}</div>
|
||||
}
|
28
examples/cms-graphcms/components/cover-image.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import cn from 'classnames'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function CoverImage({ title, url, slug }) {
|
||||
const image = (
|
||||
<img
|
||||
width={2000}
|
||||
height={1000}
|
||||
alt={`Cover Image for ${title}`}
|
||||
className={cn('shadow-small', {
|
||||
'hover:shadow-medium transition-shadow duration-200': slug,
|
||||
})}
|
||||
src={url}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="-mx-5 sm:mx-0">
|
||||
{slug ? (
|
||||
<Link as={`/posts/${slug}`} href="/posts/[slug]">
|
||||
<a aria-label={title}>{image}</a>
|
||||
</Link>
|
||||
) : (
|
||||
image
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
6
examples/cms-graphcms/components/date.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { parseISO, format } from 'date-fns'
|
||||
|
||||
export default function Date({ dateString }) {
|
||||
const date = parseISO(dateString)
|
||||
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
|
||||
}
|
30
examples/cms-graphcms/components/footer.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Container from './container'
|
||||
import { EXAMPLE_PATH } from '../lib/constants'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="bg-accent-1 border-t border-accent-2">
|
||||
<Container>
|
||||
<div className="py-28 flex flex-col lg:flex-row items-center">
|
||||
<h3 className="text-4xl lg:text-5xl font-bold tracking-tighter leading-tight text-center lg:text-left mb-10 lg:mb-0 lg:pr-4 lg:w-1/2">
|
||||
Statically Generated with Next.js.
|
||||
</h3>
|
||||
<div className="flex flex-col lg:flex-row justify-center items-center lg:pl-4 lg:w-1/2">
|
||||
<a
|
||||
href="https://nextjs.org/docs/basic-features/pages"
|
||||
className="mx-3 bg-black hover:bg-white hover:text-black border border-black text-white font-bold py-3 px-12 lg:px-8 duration-200 transition-colors mb-6 lg:mb-0"
|
||||
>
|
||||
Read Documentation
|
||||
</a>
|
||||
<a
|
||||
href={`https://github.com/zeit/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
|
||||
className="mx-3 font-bold hover:underline"
|
||||
>
|
||||
View on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
)
|
||||
}
|
12
examples/cms-graphcms/components/header.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import Link from 'next/link'
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<h2 className="text-2xl md:text-4xl font-bold tracking-tight md:tracking-tighter leading-tight mb-20 mt-8">
|
||||
<Link href="/">
|
||||
<a className="hover:underline">Blog</a>
|
||||
</Link>
|
||||
.
|
||||
</h2>
|
||||
)
|
||||
}
|
37
examples/cms-graphcms/components/hero-post.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import Avatar from '../components/avatar'
|
||||
import Date from '../components/date'
|
||||
import CoverImage from '../components/cover-image'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function HeroPost({
|
||||
title,
|
||||
coverImage,
|
||||
date,
|
||||
excerpt,
|
||||
author,
|
||||
slug,
|
||||
}) {
|
||||
return (
|
||||
<section>
|
||||
<div className="mb-8 md:mb-16">
|
||||
<CoverImage slug={slug} title={title} url={coverImage.url} />
|
||||
</div>
|
||||
<div className="mb-20 md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 md:mb-28">
|
||||
<div>
|
||||
<h3 className="mb-4 text-4xl leading-tight lg:text-6xl">
|
||||
<Link as={`/posts/${slug}`} href="/posts/[slug]">
|
||||
<a className="hover:underline">{title}</a>
|
||||
</Link>
|
||||
</h3>
|
||||
<div className="mb-4 text-lg md:mb-0">
|
||||
<Date dateString={date} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-4 text-lg leading-relaxed">{excerpt}</p>
|
||||
<Avatar name={author.name} picture={author.picture.url} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
28
examples/cms-graphcms/components/intro.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { CMS_NAME, CMS_URL } from '../lib/constants'
|
||||
|
||||
export default function Intro() {
|
||||
return (
|
||||
<section className="flex-col md:flex-row flex items-center md:justify-between mt-16 mb-16 md:mb-12">
|
||||
<h1 className="text-6xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
|
||||
Blog.
|
||||
</h1>
|
||||
<h4 className="text-center md:text-left text-lg mt-5 md:pl-8">
|
||||
A statically generated blog example using{' '}
|
||||
<a
|
||||
href="https://nextjs.org/"
|
||||
className="underline hover:text-success duration-200 transition-colors"
|
||||
>
|
||||
Next.js
|
||||
</a>{' '}
|
||||
and{' '}
|
||||
<a
|
||||
href={CMS_URL}
|
||||
className="underline hover:text-success duration-200 transition-colors"
|
||||
>
|
||||
{CMS_NAME}
|
||||
</a>
|
||||
.
|
||||
</h4>
|
||||
</section>
|
||||
)
|
||||
}
|
16
examples/cms-graphcms/components/layout.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import Alert from '../components/alert'
|
||||
import Footer from '../components/footer'
|
||||
import Meta from '../components/meta'
|
||||
|
||||
export default function Layout({ preview, children }) {
|
||||
return (
|
||||
<>
|
||||
<Meta />
|
||||
<div className="min-h-screen">
|
||||
<Alert preview={preview} />
|
||||
<main>{children}</main>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
42
examples/cms-graphcms/components/meta.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import Head from 'next/head'
|
||||
import { CMS_NAME, HOME_OG_IMAGE_URL } from '../lib/constants'
|
||||
|
||||
export default function Meta() {
|
||||
return (
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/favicon/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicon/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicon/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="/favicon/safari-pinned-tab.svg"
|
||||
color="#000000"
|
||||
/>
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#000000" />
|
||||
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#000" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
|
||||
<meta
|
||||
name="description"
|
||||
content={`A statically generated blog example using Next.js and ${CMS_NAME}.`}
|
||||
/>
|
||||
<meta property="og:image" content={HOME_OG_IMAGE_URL} />
|
||||
</Head>
|
||||
)
|
||||
}
|
24
examples/cms-graphcms/components/more-stories.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import PostPreview from '../components/post-preview'
|
||||
|
||||
export default function MoreStories({ posts }) {
|
||||
return (
|
||||
<section>
|
||||
<h2 className="mb-8 text-6xl md:text-7xl font-bold tracking-tighter leading-tight">
|
||||
More Stories
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 md:col-gap-16 lg:col-gap-32 row-gap-20 md:row-gap-32 mb-32">
|
||||
{posts.map((post) => (
|
||||
<PostPreview
|
||||
key={post.slug}
|
||||
title={post.title}
|
||||
coverImage={post.coverImage}
|
||||
date={post.date}
|
||||
author={post.author}
|
||||
slug={post.slug}
|
||||
excerpt={post.excerpt}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
10
examples/cms-graphcms/components/post-body.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import postStyles from './post-styles.module.css'
|
||||
|
||||
export default function PostBody({ content }) {
|
||||
return (
|
||||
<div
|
||||
className={`max-w-2xl mx-auto post ${postStyles.post}`}
|
||||
dangerouslySetInnerHTML={{ __html: content?.html }}
|
||||
/>
|
||||
)
|
||||
}
|
26
examples/cms-graphcms/components/post-header.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import Avatar from '../components/avatar'
|
||||
import Date from '../components/date'
|
||||
import CoverImage from '../components/cover-image'
|
||||
import PostTitle from '../components/post-title'
|
||||
|
||||
export default function PostHeader({ title, coverImage, date, author }) {
|
||||
return (
|
||||
<>
|
||||
<PostTitle>{title}</PostTitle>
|
||||
<div className="hidden md:block md:mb-12">
|
||||
<Avatar name={author.name} picture={author.picture.url} />
|
||||
</div>
|
||||
<div className="mb-8 -mx-5 md:mb-16 sm:mx-0">
|
||||
<CoverImage title={title} url={coverImage.url} />
|
||||
</div>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<div className="block mb-6 md:hidden">
|
||||
<Avatar name={author.name} picture={author.picture.url} />
|
||||
</div>
|
||||
<div className="mb-6 text-lg">
|
||||
<Date dateString={date} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
31
examples/cms-graphcms/components/post-preview.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import Avatar from '../components/avatar'
|
||||
import Date from '../components/date'
|
||||
import CoverImage from './cover-image'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function PostPreview({
|
||||
title,
|
||||
coverImage,
|
||||
date,
|
||||
excerpt,
|
||||
author,
|
||||
slug,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-5">
|
||||
<CoverImage slug={slug} title={title} url={coverImage.url} />
|
||||
</div>
|
||||
<h3 className="mb-3 text-3xl leading-snug">
|
||||
<Link as={`/posts/${slug}`} href="/posts/[slug]">
|
||||
<a className="hover:underline">{title}</a>
|
||||
</Link>
|
||||
</h3>
|
||||
<div className="mb-4 text-lg">
|
||||
<Date dateString={date} />
|
||||
</div>
|
||||
<p className="mb-4 text-lg leading-relaxed">{excerpt}</p>
|
||||
<Avatar name={author.name} picture={author.picture.url} />
|
||||
</div>
|
||||
)
|
||||
}
|
22
examples/cms-graphcms/components/post-styles.module.css
Normal file
|
@ -0,0 +1,22 @@
|
|||
.post {
|
||||
@apply text-lg leading-relaxed;
|
||||
}
|
||||
|
||||
.post p,
|
||||
.post ul,
|
||||
.post ol,
|
||||
.post blockquote {
|
||||
@apply my-6;
|
||||
}
|
||||
|
||||
.post h1 {
|
||||
@apply mt-12 mb-4 text-4xl leading-snug;
|
||||
}
|
||||
|
||||
.post h2 {
|
||||
@apply mt-12 mb-4 text-3xl leading-snug;
|
||||
}
|
||||
|
||||
.post h3 {
|
||||
@apply mt-8 mb-4 text-2xl leading-snug;
|
||||
}
|
7
examples/cms-graphcms/components/post-title.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default function PostTitle({ children }) {
|
||||
return (
|
||||
<h1 className="text-6xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-tight md:leading-none mb-12 text-center md:text-left">
|
||||
{children}
|
||||
</h1>
|
||||
)
|
||||
}
|
3
examples/cms-graphcms/components/section-separator.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function SectionSeparator() {
|
||||
return <hr className="border-accent-2 mt-28 mb-24" />
|
||||
}
|
5
examples/cms-graphcms/jsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
5
examples/cms-graphcms/lib/constants.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const EXAMPLE_PATH = 'cms-graphcms'
|
||||
export const CMS_NAME = 'GraphCMS'
|
||||
export const CMS_URL = 'https://graphcms.com/'
|
||||
export const HOME_OG_IMAGE_URL =
|
||||
'https://og-image.now.sh/Next.js%20Blog%20Example%20with%20**GraphCMS**.png?theme=light&md=1&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9ImN1cnJlbnRDb2xvciIgdmlld0JveD0iMCAwIDMzMCA3NSIgeG1sbnM6dj0iaHR0cHM6Ly92ZWN0YS5pby9uYW5vIj48cGF0aCBkPSJNMzUuMjcgMTBMOS4wOCAyNXYzMGwxNy40Ni0xMFYzNWwtOC43MyA1VjMwbDE3LjQ2LTEwdjMwTC4zNSA3MGw4LjczIDVMNDQgNTVWNWwtOC43My01eiIvPjxwYXRoIGQ9Ik04OC40NSAyMC45MUg5NnYzMC4wNmMwIDQuODgtMS41MyA4LjYtNC41OCAxMS4xN0ExNi42IDE2LjYgMCAwMTgwLjM3IDY2Yy0yLjk0LjA3LTUuODYtLjYtOC41MS0xLjk1YTEzLjMgMTMuMyAwIDAxLTUuNjUtNS43NWw2LjU2LTRjMS41MiAyLjk2IDQuMTMgNC40NCA3Ljg0IDQuNDQgMi40NCAwIDQuMzUtLjY3IDUuNzUtMi4wMiAxLjQtMS4zNSAyLjEtMy4yNyAyLjEtNS43NnYtMy40MmMtMi4yOCAzLjItNS40NyA0LjgtOS41OCA0LjgtNC4wMS4xLTcuODctMS42Mi0xMC42LTQuN0ExNi40NCAxNi40NCAwIDAxNjQgMzYuMTVhMTYuMzkgMTYuMzkgMCAwMTQuMy0xMS40MkExMy43MyAxMy43MyAwIDAxNzguODggMjBjNC4xIDAgNy4zIDEuNiA5LjU3IDQuOHYtMy44OXptLTE0LjQgMjEuNDdhOC42NiA4LjY2IDAgMDAxMi4wNCAwIDguNTYgOC41NiAwIDAwMi4zNi02LjIyQTguNDggOC40OCAwIDAwODYuMSAzMGE4LjY2IDguNjYgMCAwMC0xMi4wMyAwIDguNDggOC40OCAwIDAwLTIuMzcgNi4xNiA4LjU2IDguNTYgMCAwMDIuMzcgNi4yMnoiLz48cGF0aCBkPSJNMTA4LjggMjUuODNhOC4xNyA4LjE3IDAgMDEzLjYtNC4zN2MxLjctLjk4IDMuNjQtMS40OCA1LjYtMS40NnY4LjczYTkuMTggOS4xOCAwIDAwLTYuMzkgMS40N2MtMS44OCAxLjI1LTIuODIgMy4zNC0yLjgyIDYuMjZWNTFIMTAxVjIwLjYzaDcuOHY1LjJ6Ii8%2BPHBhdGggZD0iTTE0NC4zIDIwLjg3aDcuN3YzMC4yNmgtNy43di0zLjU2Yy0yLjMyIDIuOTUtNS41NyA0LjQzLTkuNzcgNC40M2ExMy40NyAxMy40NyAwIDAxLTEwLjI1LTQuNjNBMTYuMTQgMTYuMTQgMCAwMTEyMC4wMSAzNmMtLjExLTQuMiAxLjQyLTguMyA0LjI3LTExLjM3QTEzLjQ3IDEzLjQ3IDAgMDExMzQuNTMgMjBjNC4yIDAgNy40NSAxLjQ4IDkuNzcgNC40M3YtMy41NnptLTE0LjI2IDIxLjI5YTcuOTMgNy45MyAwIDAwNS45MyAyLjRjMi4yMy4wNiA0LjM5LS44IDUuOTYtMi40QTguNCA4LjQgMCAwMDE0NC4zIDM2YTguNCA4LjQgMCAwMC0yLjM3LTYuMTYgOC4wMyA4LjAzIDAgMDAtNS45Ni0yLjQgNy45MyA3LjkzIDAgMDAtNS45MyAyLjRBOC40NSA4LjQ1IDAgMDAxMjcuNyAzNmE4LjQ1IDguNDUgMCAwMDIuMzQgNi4xNnoiLz48cGF0aCBkPSJNMTc0LjQgMjBjMy45NC0uMDQgNy43IDEuNjggMTAuMzIgNC43MmExNi42IDE2LjYgMCAwMTQuMjcgMTEuNTcgMTYuNiAxNi42IDAgMDEtNC4yNyAxMS41OCAxMy40MyAxMy40MyAwIDAxLTEwLjMxIDQuNzFjLTQuMiAwLTcuNDMtMS41LTkuNzEtNC41VjY0SDE1N1YyMC44OWg3Ljd2My42MmMyLjI4LTMgNS41MS00LjUgOS43LTQuNXptLTcuMzYgMjIuNTZhNy44NSA3Ljg1IDAgMDA1LjkzIDIuNDQgNy45NSA3Ljk1IDAgMDA1Ljk2LTIuNDQgOC42NCA4LjY0IDAgMDAyLjM3LTYuMjcgOC42NCA4LjY0IDAgMDAtMi4zNy02LjI3IDcuOTUgNy45NSAwIDAwLTUuOTYtMi40NCA3Ljg2IDcuODYgMCAwMC01LjkzIDIuNDQgOC43IDguNyAwIDAwLTIuMzQgNi4yNyA4LjcgOC43IDAgMDAyLjM0IDYuMjd6Ii8%2BPHBhdGggZD0iTTIxMS4yNyAxOS42NWMzLjE2LS4xIDYuMjEgMS4xNiA4LjQgMy40NiAyLjIyIDIuMzEgMy4zMyA1LjUgMy4zMyA5LjU3VjUyaC04VjMzLjY3YzAtMi4xLS41Ni0zLjctMS42OC00LjgxYTYuMDggNi4wOCAwIDAwLTQuNDgtMS42NyA2LjU3IDYuNTcgMCAwMC00Ljk4IDEuOTVjLTEuMjQgMS4zLTEuODcgMy4yNS0xLjg3IDUuODZ2MTdIMTk0VjhoOHYxNi4wNWMxLjk1LTIuOTMgNS4wNC00LjQgOS4yNy00LjR6Ii8%2BPHBhdGggZD0iTTI0My43NSA1MmMtNC40Ni4xLTguNzctMS41Ni0xMS45My00LjU4QTE1LjM4IDE1LjM4IDAgMDEyMjcgMzZhMTYuMDEgMTYuMDEgMCAwMTguNDYtMTMuODcgMTcuNDIgMTcuNDIgMCAwMTE2Ljc1LjA0IDEzLjA2IDEzLjA2IDAgMDE1LjQ3IDUuODNsLTMuNDMgMS44OWE5LjY0IDkuNjQgMCAwMC00LjEtNC4zNyAxMi42NSAxMi42NSAwIDAwLTYuNC0xLjYyIDEyLjMgMTIuMyAwIDAwLTguOSAzLjUyIDExLjUgMTEuNSAwIDAwLTMuNjMgOC41OCAxMS41IDExLjUgMCAwMDMuNjMgOC41OGMyLjcxIDIuNyA2LjYgNCAxMC40NyAzLjUgMy44OC0uNDggNy4yOC0yLjcgOS4xOC01Ljk3bDMuNSAxLjk1YTE0LjM4IDE0LjM4IDAgMDEtNS43OSA1LjggMTcuMDMgMTcuMDMgMCAwMS04LjQ2IDIuMTR6Ii8%2BPHBhdGggZD0iTTI5My41NyAyMGMzLjEtLjEyIDYuMTEgMS4wNyA4LjI4IDMuMjggMi4xIDIuMTggMy4xNSA1LjEyIDMuMTUgOC44M1Y1MWgtNC4wNVYzMi4xMWMwLTIuNjItLjY4LTQuNjUtMi4wNS02LjA4YTcuMSA3LjEgMCAwMC01LjQtMi4xNSA4IDggMCAwMC02LjEzIDIuNTFjLTEuNTYgMS42OC0yLjM0IDQuMjktMi4zNCA3Ljg0VjUxaC00LjA2VjMyLjExYzAtMi42Ni0uNjQtNC43LTEuOTItNi4xMWE2LjY5IDYuNjkgMCAwMC01LjIxLTIuMTIgOC41IDguNSAwIDAwLTYuMjMgMi41NGMtMS43IDEuNy0yLjU2IDQuMy0yLjU2IDcuOFY1MUgyNjFWMjAuNzNoNC4wNXY0LjQyQTEwLjQ3IDEwLjQ3IDAgMDEyNzQuNCAyMGM0LjQgMCA3LjUgMS44MyA5LjI5IDUuNSAyLjItMy42NyA1LjQ5LTUuNSA5Ljg5LTUuNXoiLz48cGF0aCBkPSJNMzExLjMgMjguNjVhMy42NyAzLjY3IDAgMDAxLjkzIDMuMjVjMS40NS44OCAzLjA0IDEuNTQgNC43IDEuOTZsNS41MSAxLjVjMS43OS40OCAzLjQgMS40MiA0LjY2IDIuNzJhNy4wNiA3LjA2IDAgMDExLjkgNS4xNCA3LjYgNy42IDAgMDEtMy4yNiA2LjM0Yy0yLjE2IDEuNjMtNC45NSAyLjQ0LTguMzggMi40My0yLjc2LjA4LTUuNDgtLjYtNy44NS0xLjk2YTEwLjYgMTAuNiAwIDAxLTQuNTEtNS4wN2wzLjYtMmE3LjI0IDcuMjQgMCAwMDMuMjMgMy43NWMxLjY3Ljk1IDMuNiAxLjQzIDUuNTQgMS4zOCAxLjgzLjA3IDMuNjQtLjM0IDUuMjUtMS4xOGEzLjk1IDMuOTUgMCAwMDIuMTEtMy42OWMuMDMtMS4zNC0uNy0yLjYtMS45LTMuMjlhMTcgMTcgMCAwMC00LjY4LTEuOTdsLTUuNTItMS41Yy0xLjc4LS40OC0zLjQtMS40LTQuNjgtMi42OWE2LjgyIDYuODIgMCAwMS0xLjkyLTUuMDUgNy42OSA3LjY5IDAgMDEzLjEzLTYuMjIgMTIuMTYgMTIuMTYgMCAwMTcuODYtMi41YzIuNC0uMDQgNC43OC41NCA2Ljg2IDEuN2ExMC41IDEwLjUgMCAwMTQuMzYgNC41NGwtMy41NSAxLjk1Yy0xLjIzLTIuODUtMy44LTQuMjgtNy42Ny00LjI4YTguOCA4LjggMCAwMC00Ljc0IDEuMjMgMy45MiAzLjkyIDAgMDAtMS45OSAzLjUxeiIvPjwvc3ZnPg%3D%3D'
|
150
examples/cms-graphcms/lib/graphcms.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
async function fetchAPI(query, { variables, preview } = {}) {
|
||||
const res = await fetch(process.env.GRAPHCMS_PROJECT_API, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${
|
||||
preview
|
||||
? process.env.GRAPHCMS_DEV_AUTH_TOKEN
|
||||
: process.env.GRAPHCMS_PROD_AUTH_TOKEN
|
||||
}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
})
|
||||
const json = await res.json()
|
||||
|
||||
if (json.errors) {
|
||||
console.log(process.env.NEXT_EXAMPLE_CMS_GCMS_PROJECT_ID)
|
||||
console.error(json.errors)
|
||||
throw new Error('Failed to fetch API')
|
||||
}
|
||||
|
||||
return json.data
|
||||
}
|
||||
|
||||
export async function getPreviewPostBySlug(slug) {
|
||||
const data = await fetchAPI(
|
||||
`
|
||||
query PostBySlug($slug: String!, $stage: Stage!) {
|
||||
post(where: {slug: $slug}, stage: $stage) {
|
||||
slug
|
||||
}
|
||||
}`,
|
||||
{
|
||||
preview: true,
|
||||
variables: {
|
||||
stage: 'DRAFT',
|
||||
slug,
|
||||
},
|
||||
}
|
||||
)
|
||||
return data.post
|
||||
}
|
||||
|
||||
export async function getAllPostsWithSlug() {
|
||||
const data = await fetchAPI(`
|
||||
{
|
||||
posts {
|
||||
slug
|
||||
}
|
||||
}
|
||||
`)
|
||||
return data.posts
|
||||
}
|
||||
|
||||
export async function getAllPostsForHome(preview) {
|
||||
const data = await fetchAPI(
|
||||
`
|
||||
{
|
||||
posts(orderBy: date_DESC, first: 20) {
|
||||
title
|
||||
slug
|
||||
excerpt
|
||||
date
|
||||
coverImage {
|
||||
url(transformation: {
|
||||
image: {
|
||||
resize: {
|
||||
fit:crop,
|
||||
width:2000,
|
||||
height:1000
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
author {
|
||||
name
|
||||
picture {
|
||||
url(transformation: {
|
||||
image: {
|
||||
resize: {
|
||||
width:100,
|
||||
height:100,
|
||||
fit:crop
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ preview }
|
||||
)
|
||||
return data.posts
|
||||
}
|
||||
|
||||
export async function getPostAndMorePosts(slug, preview) {
|
||||
const data = await fetchAPI(
|
||||
`
|
||||
query PostBySlug($slug: String!, $stage: Stage!) {
|
||||
post(stage: $stage, where: {slug: $slug}) {
|
||||
title
|
||||
slug
|
||||
content {
|
||||
html
|
||||
}
|
||||
date
|
||||
ogImage: coverImage {
|
||||
url(transformation: {image: {resize: {fit: crop, width: 2000, height: 1000}}})
|
||||
}
|
||||
coverImage {
|
||||
url(transformation: {image: {resize: {fit: crop, width: 2000, height: 1000}}})
|
||||
}
|
||||
author {
|
||||
name
|
||||
picture {
|
||||
url(transformation: {image: {resize: {fit: crop, width: 100, height: 100}}})
|
||||
}
|
||||
}
|
||||
}
|
||||
morePosts: posts(orderBy: date_DESC, first: 2, where: {slug_not_in: [$slug]}) {
|
||||
title
|
||||
slug
|
||||
excerpt
|
||||
date
|
||||
coverImage {
|
||||
url(transformation: {image: {resize: {fit: crop, width: 2000, height: 1000}}})
|
||||
}
|
||||
author {
|
||||
name
|
||||
picture {
|
||||
url(transformation: {image: {resize: {fit: crop, width: 100, height: 100}}})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
preview,
|
||||
variables: {
|
||||
stage: preview ? 'DRAFT' : 'PUBLISHED',
|
||||
slug,
|
||||
},
|
||||
}
|
||||
)
|
||||
return data
|
||||
}
|
21
examples/cms-graphcms/package.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "cms-graphcms",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "2.2.6",
|
||||
"date-fns": "2.10.0",
|
||||
"next": "latest",
|
||||
"react": "^16.13.0",
|
||||
"react-dom": "^16.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"postcss-flexbugs-fixes": "^4.2.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"tailwindcss": "^1.4.6"
|
||||
}
|
||||
}
|
7
examples/cms-graphcms/pages/_app.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import '../styles/index.css'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
|
||||
export default MyApp
|
15
examples/cms-graphcms/pages/_document.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
8
examples/cms-graphcms/pages/api/exit-preview.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default async function handler(_, res) {
|
||||
// Exit the current user from "Preview Mode". This function accepts no args.
|
||||
res.clearPreviewData()
|
||||
|
||||
// Redirect the user back to the index page.
|
||||
res.writeHead(307, { Location: '/' })
|
||||
res.end()
|
||||
}
|
28
examples/cms-graphcms/pages/api/preview.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { getPreviewPostBySlug } from '../../lib/graphcms'
|
||||
|
||||
export default async function handler(req, res) {
|
||||
// Check the secret and next parameters
|
||||
// This secret should only be known to this API route and the CMS
|
||||
if (
|
||||
req.query.secret !== process.env.GRAPHCMS_PREVIEW_SECRET ||
|
||||
!req.query.slug
|
||||
) {
|
||||
return res.status(401).json({ message: 'Invalid token' })
|
||||
}
|
||||
|
||||
// Fetch the headless CMS to check if the provided `slug` exists
|
||||
const post = await getPreviewPostBySlug(req.query.slug)
|
||||
|
||||
// If the slug doesn't exist prevent preview mode from being enabled
|
||||
if (!post) {
|
||||
return res.status(401).json({ message: 'Invalid slug' })
|
||||
}
|
||||
|
||||
// Enable Preview Mode by setting the cookies
|
||||
res.setPreviewData({})
|
||||
|
||||
// Redirect to the path from the fetched post
|
||||
// We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
|
||||
res.writeHead(307, { Location: `/posts/${post.slug}` })
|
||||
res.end()
|
||||
}
|
43
examples/cms-graphcms/pages/index.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
import Container from '../components/container'
|
||||
import MoreStories from '../components/more-stories'
|
||||
import HeroPost from '../components/hero-post'
|
||||
import Intro from '../components/intro'
|
||||
import Layout from '../components/layout'
|
||||
import { getAllPostsForHome } from '../lib/graphcms'
|
||||
import Head from 'next/head'
|
||||
import { CMS_NAME } from '../lib/constants'
|
||||
|
||||
export default function Index({ posts, preview }) {
|
||||
const heroPost = posts[0]
|
||||
const morePosts = posts.slice(1)
|
||||
return (
|
||||
<>
|
||||
<Layout preview={preview}>
|
||||
<Head>
|
||||
<title>Next.js Blog Example with {CMS_NAME}</title>
|
||||
</Head>
|
||||
<Container>
|
||||
<Intro />
|
||||
{heroPost && (
|
||||
<HeroPost
|
||||
title={heroPost.title}
|
||||
coverImage={heroPost.coverImage}
|
||||
date={heroPost.date}
|
||||
author={heroPost.author}
|
||||
slug={heroPost.slug}
|
||||
excerpt={heroPost.excerpt}
|
||||
/>
|
||||
)}
|
||||
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
|
||||
</Container>
|
||||
</Layout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps({ preview = false }) {
|
||||
const posts = (await getAllPostsForHome(preview)) || []
|
||||
return {
|
||||
props: { posts, preview },
|
||||
}
|
||||
}
|
73
examples/cms-graphcms/pages/posts/[slug].js
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import ErrorPage from 'next/error'
|
||||
import Container from 'components/container'
|
||||
import PostBody from 'components/post-body'
|
||||
import MoreStories from 'components/more-stories'
|
||||
import Header from 'components/header'
|
||||
import PostHeader from 'components/post-header'
|
||||
import SectionSeparator from 'components/section-separator'
|
||||
import Layout from 'components/layout'
|
||||
import { getAllPostsWithSlug, getPostAndMorePosts } from 'lib/graphcms'
|
||||
import PostTitle from 'components/post-title'
|
||||
import Head from 'next/head'
|
||||
import { CMS_NAME } from 'lib/constants'
|
||||
|
||||
export default function Post({ post, morePosts, preview }) {
|
||||
const router = useRouter()
|
||||
|
||||
if (!router.isFallback && !post?.slug) {
|
||||
return <ErrorPage statusCode={404} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout preview={preview}>
|
||||
<Container>
|
||||
<Header />
|
||||
{router.isFallback ? (
|
||||
<PostTitle>Loading…</PostTitle>
|
||||
) : (
|
||||
<>
|
||||
<article>
|
||||
<Head>
|
||||
<title>
|
||||
{post.title} | Next.js Blog Example with {CMS_NAME}
|
||||
</title>
|
||||
{/* <meta property="og:image" content={post.ogImage.url} /> */}
|
||||
</Head>
|
||||
<PostHeader
|
||||
title={post.title}
|
||||
coverImage={post.coverImage}
|
||||
date={post.date}
|
||||
author={post.author}
|
||||
/>
|
||||
<PostBody content={post.content} />
|
||||
</article>
|
||||
<SectionSeparator />
|
||||
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params, preview = false }) {
|
||||
const data = await getPostAndMorePosts(params.slug, preview)
|
||||
return {
|
||||
props: {
|
||||
preview,
|
||||
post: data.post,
|
||||
morePosts: data.morePosts || [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getAllPostsWithSlug()
|
||||
return {
|
||||
paths: posts.map(({ slug }) => ({
|
||||
params: { slug },
|
||||
})),
|
||||
fallback: true,
|
||||
}
|
||||
}
|
18
examples/cms-graphcms/postcss.config.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
'tailwindcss',
|
||||
'postcss-flexbugs-fixes',
|
||||
[
|
||||
'postcss-preset-env',
|
||||
{
|
||||
autoprefixer: {
|
||||
flexbox: 'no-2009',
|
||||
},
|
||||
stage: 3,
|
||||
features: {
|
||||
'custom-properties': false,
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
}
|
BIN
examples/cms-graphcms/public/favicon/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
examples/cms-graphcms/public/favicon/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
examples/cms-graphcms/public/favicon/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
9
examples/cms-graphcms/public/favicon/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/favicons/mstile-150x150.png"/>
|
||||
<TileColor>#000000</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
examples/cms-graphcms/public/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
examples/cms-graphcms/public/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 880 B |
BIN
examples/cms-graphcms/public/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
examples/cms-graphcms/public/favicon/mstile-150x150.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
33
examples/cms-graphcms/public/favicon/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M4785 10234 c-22 -2 -92 -9 -155 -14 -1453 -131 -2814 -915 -3676
|
||||
-2120 -480 -670 -787 -1430 -903 -2235 -41 -281 -46 -364 -46 -745 0 -381 5
|
||||
-464 46 -745 278 -1921 1645 -3535 3499 -4133 332 -107 682 -180 1080 -224
|
||||
155 -17 825 -17 980 0 687 76 1269 246 1843 539 88 45 105 57 93 67 -8 6 -383
|
||||
509 -833 1117 l-818 1105 -1025 1517 c-564 834 -1028 1516 -1032 1516 -4 1 -8
|
||||
-673 -10 -1496 -3 -1441 -4 -1499 -22 -1533 -26 -49 -46 -69 -88 -91 -32 -16
|
||||
-60 -19 -211 -19 l-173 0 -46 29 c-30 19 -52 44 -67 73 l-21 45 2 2005 3 2006
|
||||
31 39 c16 21 50 48 74 61 41 20 57 22 230 22 204 0 238 -8 291 -66 15 -16 570
|
||||
-852 1234 -1859 664 -1007 1572 -2382 2018 -3057 l810 -1227 41 27 c363 236
|
||||
747 572 1051 922 647 743 1064 1649 1204 2615 41 281 46 364 46 745 0 381 -5
|
||||
464 -46 745 -278 1921 -1645 3535 -3499 4133 -327 106 -675 179 -1065 223 -96
|
||||
10 -757 21 -840 13z m2094 -3094 c48 -24 87 -70 101 -118 8 -26 10 -582 8
|
||||
-1835 l-3 -1798 -317 486 -318 486 0 1307 c0 845 4 1320 10 1343 16 56 51 100
|
||||
99 126 41 21 56 23 213 23 148 0 174 -2 207 -20z"/>
|
||||
<path d="M7843 789 c-35 -22 -46 -37 -15 -20 22 13 58 40 52 41 -3 0 -20 -10
|
||||
-37 -21z"/>
|
||||
<path d="M7774 744 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7724 714 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7674 684 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||
<path d="M7598 644 c-38 -20 -36 -28 2 -9 17 9 30 18 30 20 0 7 -1 6 -32 -11z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
19
examples/cms-graphcms/public/favicon/site.webmanifest
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "Next.js",
|
||||
"short_name": "Next.js",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#000000",
|
||||
"display": "standalone"
|
||||
}
|
5
examples/cms-graphcms/styles/index.css
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* purgecss start ignore */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
/* purgecss end ignore */
|
||||
@tailwind utilities;
|
33
examples/cms-graphcms/tailwind.config.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
module.exports = {
|
||||
purge: ['./components/**/*.js', './pages/**/*.js'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'accent-1': '#FAFAFA',
|
||||
'accent-2': '#EAEAEA',
|
||||
'accent-7': '#333',
|
||||
success: '#0070f3',
|
||||
cyan: '#79FFE1',
|
||||
},
|
||||
spacing: {
|
||||
28: '7rem',
|
||||
},
|
||||
letterSpacing: {
|
||||
tighter: '-.04em',
|
||||
},
|
||||
lineHeight: {
|
||||
tight: 1.2,
|
||||
},
|
||||
fontSize: {
|
||||
'5xl': '2.5rem',
|
||||
'6xl': '2.75rem',
|
||||
'7xl': '4.5rem',
|
||||
'8xl': '6.25rem',
|
||||
},
|
||||
boxShadow: {
|
||||
small: '0 5px 10px rgba(0, 0, 0, 0.12)',
|
||||
medium: '0 8px 30px rgba(0, 0, 0, 0.12)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-4-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)f
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-6-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [Strapi](/examples/cms-strapi)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-7-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|
|
@ -24,6 +24,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
|
|||
- [Cosmic](/examples/cms-cosmic)
|
||||
- [ButterCMS](/examples/cms-buttercms)
|
||||
- [Storyblok](/examples/cms-storyblok)
|
||||
- [GraphCMS](/examples/cms-graphcms)
|
||||
- [Blog Starter](/examples/blog-starter)
|
||||
|
||||
## How to use
|
||||
|
|