未验证 提交 1a7a14e5 编写于 作者: L Luis Alvarez D 提交者: GitHub

[Examples] add Contentful example (#12323)

* Added example

* Readme fixes

* Preview mode fix

* Added rich test post content

* Added demo

* Apply suggestions from code review

* Update examples/cms-contentful/README.md

* Apply suggestions from code review

* Apply suggestions from code review

* Now → Vercel
Co-authored-by: NShu Uesugi <shu@chibicode.com>
上级 0713ba4e
NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID=
NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN=
NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN=
NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET=
\ No newline at end of file
.env
.now
\ No newline at end of file
# A statically generated blog example using Next.js and Contentful
This example showcases Next.js's [Static Generation](/docs/basic-features/pages.md) feature using [Contentful](https://www.contentful.com/) as the data source.
## Demo
### [https://next-blog-contentful.now.sh/](https://next-blog-contentful.now.sh/)
### Related examples
- [Blog Starter](/examples/blog-starter)
- [DatoCMS](/examples/cms-datocms)
- [TakeShape](/examples/cms-takeshape)
- [Sanity](/examples/cms-sanity)
## How to use
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-contentful cms-contentful-app
# or
yarn create next-app --example cms-contentful cms-contentful-app
```
## Configuration
### Step 1. Create an account and a space on Contentful
First, [create an account on Contentful](https://www.contentful.com/sign-up/).
After creating an account, create a new **space** from the [dashboard](https://app.contentful.com/) and assign to it any name of your liking.
### Step 2. Create an `author` content type
From your contentful space, go to **Content model** and add a new content type:
- **Api Identifier** should be `author`
Once the content model is saved, let's add these fields (you don't have to modify the settings unless specified):
- `name` - **Text** field. **Field ID** should be set to `name`
- `picture` - **Media** field. **Field ID** should be set to `picture`
Save the content type and continue.
### Step 3. Create a `post` type
From your contentful space, go to **Content model** and add a new content type:
- **Api Identifier** should be `post`
Next, add these fields (you don't have to modify the settings unless specified):
- `title` - **Text** field
- `content` - **Rich text** field
- `excerpt` - **Text** field. Enable **Long text, full-text search**
- `coverImage` - **Media** field
- `date` - **Date and time** field
- `slug` - **Text** field. You can optionally go to the settings of this field, and under **Appearance**, select **Slug** to display it as a slug of the `title` field.
- `author` - **Reference** field
Save the content type and continue.
### Step 4. Populate Content
Go to the **Content** page in your space, then click on **Add entry** and select the **author** content type:
- You just need **1 author entry**.
- Use dummy data for the text.
- For the image, you can download one from [Unsplash](https://unsplash.com/).
Next, add a new entry to **post**:
- We recommend creating at least **2 post entries**.
- Use dummy data for the text.
- You can write markdown for the **content** field.
- For images, you can download them from [Unsplash](https://unsplash.com/).
- Pick the **author** you created earlier.
**Important:** For each entry, you need to click on **Publish**. If not, the entry will be in draft state.
### Step 5. Set up environment variables
From your contentful space, go to **Settings > API keys**. There should be an example Content delivery / preview token - you can use this API key. (You may also create a new key.)
Next, copy the `.env.example` file in this directory to `.env` (which will be ignored by Git):
```bash
cp .env.example .env
```
Then set each variable on `.env`:
- `NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID` should be the **Space ID** field of your API Key
- `NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN` should be the **Content Delivery API - access token** field of your API key
- `NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN` should be the **Content Preview API - access token** field of your API key
- `NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET` should be any value you want. It must be URL friendly as the dashboard will send it as a query parameter to enable preview mode
Your `.env` file should look like this:
```bash
NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID=...
NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN=...
NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN=...
NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET=...
```
### Step 6. 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 7. Try preview mode
On your contentful space, go to **Settings > Content preview**, then add a new content preview for development.
The **Name** field may be anything, like `Development`. Then, under **Content preview URLs**, check **post** and set its value to:
```
http://localhost:3000/api/preview?secret=<NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET>&slug={entry.fields.slug}
```
Replace `<NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET>` with its respective value in `.env`.
Once saved, go to one of the posts you've created and:
- **Update the title**. For example, you can add `[Draft]` in front of the title.
- The state of the post will switch to **CHANGED** automatically. **Do not** publish it. By doing this, the post will be in draft state.
- In the sidebar, you should see the **Open preview** button. Click on it!
You should now be able to see the updated title. To exit preview mode, you can click on **Click here to exit preview mode** at the top of the page.
### Step 8. Deploy on Vercel
You can deploy this app to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
Install [Vercel CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace each variable with the corresponding strings in `.env`:
```bash
now secrets add next_example_contentful_space_id <NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID>
now secrets add next_example_cms_contentful_access_token <NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN>
now secrets add next_example_cms_contentful_preview_access_token <NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN>
now secrets add next_example_cms_contentful_preview_secret <NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET>
```
Then push the project to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) to deploy.
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 is 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>
)
}
export default function Avatar({ name, picture }) {
return (
<div className="flex items-center">
<img
src={picture.url}
className="w-12 h-12 rounded-full mr-4"
alt={name}
/>
<div className="text-xl font-bold">{name}</div>
</div>
)
}
export default function Container({ children }) {
return <div className="container mx-auto px-5">{children}</div>
}
import cn from 'classnames'
import Link from 'next/link'
export default function CoverImage({ title, url, slug }) {
const image = (
<img
src={url}
alt={`Cover Image for ${title}`}
className={cn('shadow-small', {
'hover:shadow-medium transition-shadow duration-200': slug,
})}
/>
)
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>
)
}
import { format } from 'date-fns'
export default ({ dateString }) => (
<time dateTime={dateString}>
{format(new Date(dateString), 'LLLL d, yyyy')}
</time>
)
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>
)
}
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>
)
}
import Link from 'next/link'
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from '../components/cover-image'
export default function HeroPost({
title,
coverImage,
date,
excerpt,
author,
slug,
}) {
return (
<section>
<div className="mb-8 md:mb-16">
<CoverImage title={title} slug={slug} url={coverImage.url} />
</div>
<div className="md:grid md:grid-cols-2 md:col-gap-16 lg:col-gap-8 mb-20 md:mb-28">
<div>
<h3 className="mb-4 text-4xl lg:text-6xl leading-tight">
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a className="hover:underline">{title}</a>
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<Date dateString={date} />
</div>
</div>
<div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
</div>
</section>
)
}
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>
)
}
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 />
</>
)
}
.markdown {
@apply text-lg leading-relaxed;
}
.markdown p,
.markdown ul,
.markdown ol,
.markdown blockquote {
@apply my-6;
}
.markdown h2 {
@apply text-3xl mt-12 mb-4 leading-snug;
}
.markdown h3 {
@apply text-2xl mt-8 mb-4 leading-snug;
}
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>
)
}
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>
)
}
import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
import markdownStyles from './markdown-styles.module.css'
export default function PostBody({ content }) {
return (
<div className="max-w-2xl mx-auto">
<div className={markdownStyles['markdown']}>
{documentToReactComponents(content)}
</div>
</div>
)
}
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">
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
<div className="mb-8 md:mb-16 -mx-5 sm:mx-0">
<CoverImage title={title} url={coverImage.url} />
</div>
<div className="max-w-2xl mx-auto">
<div className="block md:hidden mb-6">
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
<div className="mb-6 text-lg">
<Date dateString={date} />
</div>
</div>
</>
)
}
import Link from 'next/link'
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from './cover-image'
export default function PostPreview({
title,
coverImage,
date,
excerpt,
author,
slug,
}) {
return (
<div>
<div className="mb-5">
<CoverImage title={title} slug={slug} url={coverImage.url} />
</div>
<h3 className="text-3xl mb-3 leading-snug">
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a className="hover:underline">{title}</a>
</Link>
</h3>
<div className="text-lg mb-4">
<Date dateString={date} />
</div>
<p className="text-lg leading-relaxed mb-4">{excerpt}</p>
{author && <Avatar name={author.name} picture={author.picture} />}
</div>
)
}
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>
)
}
export default function SectionSeparator() {
return <hr className="border-accent-2 mt-28 mb-24" />
}
import { createClient } from 'contentful'
const client = createClient({
space: process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN,
})
const previewClient = createClient({
space: process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN,
host: 'preview.contentful.com',
})
const getClient = preview => (preview ? previewClient : client)
function parseAuthor({ fields }) {
return {
name: fields.name,
picture: fields.picture.fields.file,
}
}
function parsePost({ fields }) {
return {
title: fields.title,
slug: fields.slug,
date: fields.date,
content: fields.content,
excerpt: fields.excerpt,
coverImage: fields.coverImage.fields.file,
author: parseAuthor(fields.author),
}
}
function parsePostEntries(entries, cb = parsePost) {
return entries?.items?.map(cb)
}
export async function getPreviewPostBySlug(slug) {
const entries = await getClient(true).getEntries({
content_type: 'post',
limit: 1,
'fields.slug[in]': slug,
})
return parsePostEntries(entries)[0]
}
export async function getAllPostsWithSlug() {
const entries = await client.getEntries({
content_type: 'post',
select: 'fields.slug',
})
return parsePostEntries(entries, post => post.fields)
}
export async function getAllPostsForHome(preview) {
const entries = await getClient(preview).getEntries({
content_type: 'post',
order: '-fields.date',
})
return parsePostEntries(entries)
}
export async function getPostAndMorePosts(slug, preview) {
const entry = await getClient(preview).getEntries({
content_type: 'post',
limit: 1,
'fields.slug[in]': slug,
})
const entries = await getClient(preview).getEntries({
content_type: 'post',
limit: 2,
order: '-fields.date',
'fields.slug[nin]': slug,
})
return {
post: parsePostEntries(entry)[0],
morePosts: parsePostEntries(entries),
}
}
export const EXAMPLE_PATH = 'cms-contentful'
export const CMS_NAME = 'Contentful'
export const CMS_URL = 'https://www.contentful.com'
export const HOME_OG_IMAGE_URL =
'https://og-image.now.sh/Next.js%20Blog%20Example%20with%20**Contentful**.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOSIgaGVpZ2h0PSIzMiI%2BCiAgPHBhdGggZmlsbD0iI0ZGRDg1RiIgZD0iTTkuNyAyMi4zQzggMjAuNyA3IDE4LjUgNyAxNnMxLTQuNyAyLjYtNi4zYzEuNC0xLjQgMS40LTMuNiAwLTVzLTMuNi0xLjQtNSAwQzEuOCA3LjYgMCAxMS42IDAgMTZzMS44IDguNCA0LjcgMTEuM2MxLjQgMS40IDMuNiAxLjQgNSAwIDEuMy0xLjQgMS4zLTMuNiAwLTV6Ij48L3BhdGg%2BCiAgPHBhdGggZmlsbD0iIzNCQjRFNyIgZD0iTTkuNyA5LjdDMTEuMyA4IDEzLjUgNyAxNiA3czQuNyAxIDYuMyAyLjZjMS40IDEuNCAzLjYgMS40IDUgMHMxLjQtMy42IDAtNUMyNC40IDEuOCAyMC40IDAgMTYgMFM3LjYgMS44IDQuNyA0LjdjLTEuNCAxLjQtMS40IDMuNiAwIDUgMS40IDEuMyAzLjYgMS4zIDUgMHoiPjwvcGF0aD4KICA8cGF0aCBmaWxsPSIjRUQ1QzY4IiBkPSJNMjIuMyAyMi4zQzIwLjcgMjQgMTguNSAyNSAxNiAyNXMtNC43LTEtNi4zLTIuNmMtMS40LTEuNC0zLjYtMS40LTUgMHMtMS40IDMuNiAwIDVDNy42IDMwLjIgMTEuNiAzMiAxNiAzMnM4LjQtMS44IDExLjMtNC43YzEuNC0xLjQgMS40LTMuNiAwLTUtMS40LTEuMy0zLjYtMS4zLTUgMHoiPjwvcGF0aD4KICA8Y2lyY2xlIGN4PSI3LjIiIGN5PSI3LjIiIHI9IjMuNSIgZmlsbD0iIzMwOEJDNSI%2BPC9jaXJjbGU%2BCiAgPGNpcmNsZSBjeD0iNy4yIiBjeT0iMjQuOCIgcj0iMy41IiBmaWxsPSIjRDU0NjVGIj48L2NpcmNsZT4KPC9zdmc%2B'
require('dotenv').config()
module.exports = {
env: {
NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID:
process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID,
NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN:
process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN,
NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN:
process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN,
},
}
{
"env": {
"NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID": "@next_example_contentful_space_id",
"NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN": "@next_example_cms_contentful_access_token",
"NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN": "@next_example_cms_contentful_preview_access_token",
"NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET": "@next_example_cms_contentful_preview_secret"
},
"build": {
"env": {
"NEXT_EXAMPLE_CMS_CONTENTFUL_SPACE_ID": "@next_example_contentful_space_id",
"NEXT_EXAMPLE_CMS_CONTENTFUL_ACCESS_TOKEN": "@next_example_cms_contentful_access_token",
"NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_ACCESS_TOKEN": "@next_example_cms_contentful_preview_access_token"
}
}
}
{
"name": "cms-contentful",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@contentful/rich-text-react-renderer": "13.4.0",
"classnames": "2.2.6",
"contentful": "7.14.4",
"date-fns": "2.10.0",
"next": "9.3.6",
"react": "^16.13.0",
"react-dom": "^16.13.0"
},
"devDependencies": {
"@fullhuman/postcss-purgecss": "^2.1.0",
"dotenv": "8.2.0",
"postcss-preset-env": "^6.7.0",
"tailwindcss": "^1.2.0"
}
}
import '../styles/index.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
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>
)
}
}
export default async (_, 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()
}
import { getPreviewPostBySlug } from '../../lib/api'
export default async (req, res) => {
const { secret, slug } = req.query
if (
secret !== process.env.NEXT_EXAMPLE_CMS_CONTENTFUL_PREVIEW_SECRET ||
!slug
) {
return res.status(401).json({ message: 'Invalid token' })
}
// Fetch the headless CMS to check if the provided `slug` exists
const post = await getPreviewPostBySlug(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}` })
const url = `/posts/${post.slug}`
res.write(
`<!DOCTYPE html><html><head><meta http-equiv="Refresh" content="0; url=${url}" />
<script>window.location.href = '${url}'</script>
</head>`
)
res.end()
}
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/api'
import Head from 'next/head'
import { CMS_NAME } from '../lib/constants'
export default function Index({ preview, allPosts }) {
const heroPost = allPosts[0]
const morePosts = allPosts.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 allPosts = await getAllPostsForHome(preview)
return {
props: { preview, allPosts },
}
}
import { useRouter } from 'next/router'
import Head from 'next/head'
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/api'
import PostTitle from '../../components/post-title'
import { CMS_NAME } from '../../lib/constants'
export default function Post({ post, morePosts, preview }) {
const router = useRouter()
if (!router.isFallback && !post) {
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.coverImage.url} />
</Head>
<PostHeader
title={post.title}
coverImage={post.coverImage}
date={post.date}
author={post.author}
/>
<PostBody content={post.content} />
</article>
<SectionSeparator />
{morePosts && 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 allPosts = await getAllPostsWithSlug()
return {
paths: allPosts?.map(({ slug }) => `/posts/${slug}`) || [],
fallback: true,
}
}
module.exports = {
plugins: [
'tailwindcss',
process.env.NODE_ENV === 'production'
? [
'@fullhuman/postcss-purgecss',
{
content: [
'./pages/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}',
],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || [],
},
]
: undefined,
'postcss-preset-env',
],
}
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/favicons/mstile-150x150.png"/>
<TileColor>#000000</TileColor>
</tile>
</msapplication>
</browserconfig>
<?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>
{
"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"
}
/* purgecss start ignore */
@tailwind base;
@tailwind components;
/* purgecss end ignore */
@tailwind utilities;
module.exports = {
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)',
},
},
},
}
{
"Main": {
"name": {
"type": "Text",
"config": {
"label": "name"
}
},
"picture": {
"type": "Image",
"config": {
"constraint": {},
"thumbnails": [],
"label": "picture"
}
}
}
}
{
"Main": {
"title": {
"type": "StructuredText",
"config": {
"single": "heading1, heading2, heading3, heading4, heading5, heading6",
"label": "title"
}
},
"content": {
"type": "StructuredText",
"config": {
"multi": "paragraph, preformatted, heading1, heading2, heading3, heading4, heading5, heading6, strong, em, hyperlink, image, embed, list-item, o-list-item, o-list-item",
"label": "content"
}
},
"excerpt": {
"type": "Text",
"config": {
"label": "excerpt"
}
},
"coverimage": {
"type": "Image",
"config": {
"constraint": {},
"thumbnails": [],
"label": "coverimage"
}
},
"date": {
"type": "Date",
"config": {
"label": "date"
}
},
"author": {
"type": "Link",
"config": {
"select": "document",
"customtypes": ["author"],
"label": "author"
}
},
"uid": {
"type": "UID",
"config": {
"label": "slug"
}
}
}
}
......@@ -152,9 +152,9 @@ You should now be able to see the updated title. To exit the preview mode, you c
You can deploy this app to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Now CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
Install [Now CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_DATOCMS_API_TOKEN>` and `<NEXT_EXAMPLE_CMS_DATOCMS_PREVIEW_SECRET>` with the corresponding strings in `.env`.
Install [Vercel CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_DATOCMS_API_TOKEN>` and `<NEXT_EXAMPLE_CMS_DATOCMS_PREVIEW_SECRET>` with the corresponding strings in `.env`.
```
now secrets add next_example_cms_datocms_api_token <NEXT_EXAMPLE_CMS_DATOCMS_API_TOKEN>
......
......@@ -145,15 +145,15 @@ Once saved, go to one of the posts you've created and:
- Click **Save**, but **DO NOT** click **Publish**. By doing this, the post will be in draft state.
- Right next to the **Publish** button you should see the **Preview** button, displayed with an eye icon. Click on it!
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 of the page.
You should now be able to see the updated title. To exit preview mode, you can click on **Click here to exit preview mode** at the top of the page.
### Step 8. Deploy on Vercel
You can deploy this app to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Now CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
Install [Now CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_PRISMIC_API_TOKEN>` and `<NEXT_EXAMPLE_CMS_PRISMIC_REPOSITORY_NAME>` with the corresponding strings in `.env`:
Install [Vercel CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_PRISMIC_API_TOKEN>` and `<NEXT_EXAMPLE_CMS_PRISMIC_REPOSITORY_NAME>` with the corresponding strings in `.env`:
```bash
now secrets add next_example_cms_prismic_api_token <NEXT_EXAMPLE_CMS_PRISMIC_API_TOKEN>
......
......@@ -148,9 +148,9 @@ You should now be able to see the updated title. To exit the preview mode, you c
You can deploy this app to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Now CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
Install [Now CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace each variable with the corresponding strings in `.env`.
Install [Vercel CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace each variable with the corresponding strings in `.env`.
```
now secrets add next_example_cms_sanity_preview_secret <NEXT_EXAMPLE_CMS_SANITY_PREVIEW_SECRET>
......
......@@ -154,9 +154,9 @@ You should now be able to see this post. To exit the preview mode, you can click
You can deploy this app to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Now CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
Install [Now CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_TAKESHAPE_API_KEY>` and `<NEXT_EXAMPLE_CMS_TAKESHAPE_PREVIEW_SECRET>` with the corresponding strings in `.env`.
Install [Vercel CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_TAKESHAPE_API_KEY>` and `<NEXT_EXAMPLE_CMS_TAKESHAPE_PREVIEW_SECRET>` with the corresponding strings in `.env`.
```
now secrets add next_example_cms_takeshape_api_key <NEXT_EXAMPLE_CMS_TAKESHAPE_API_KEY>
......
......@@ -43,9 +43,9 @@ Then set each variable on `.env`:
- `MAGIC_PUBLISHABLE_KEY` should look like `pk_test_abc` or `pk_live_ABC`
- `MAGIC_SECRET_KEY` should look like `sk_test_ABC` or `sk_live_ABC`
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Now CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
Install [Now CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<MAGIC_PUBLISHABLE_KEY>` and `<MAGIC_SECRET_KEY>` with the corresponding strings in `.env`:
Install [Vercel CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<MAGIC_PUBLISHABLE_KEY>` and `<MAGIC_SECRET_KEY>` with the corresponding strings in `.env`:
```bash
now secrets add next_example_magic_publishable_key <MAGIC_PUBLISHABLE_KEY>
......
......@@ -85,9 +85,9 @@ Your todo app should be up and running on [http://localhost:3000](http://localho
You can deploy this app to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Now CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
To deploy on Vercel, you need to set the environment variables with **Now Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/now-cli#commands/secrets)).
Install [Now CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_DATOCMS_API_TOKEN>` and `<NEXT_EXAMPLE_CMS_DATOCMS_PREVIEW_SECRET>` with the corresponding strings in `.env`.
Install [Vercel CLI](https://vercel.com/download), log in to your account from the CLI, and run the following commands to add the environment variables. Replace `<NEXT_EXAMPLE_CMS_DATOCMS_API_TOKEN>` and `<NEXT_EXAMPLE_CMS_DATOCMS_PREVIEW_SECRET>` with the corresponding strings in `.env`.
```
now secrets add userbase-app-id <USERBASE_APP_ID>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册