[Examples] Add WordPress Blog (#13194)

* Added most of the stuff

* Updated pages

* Removed unrequired deps

* API fixes

* Fixes fixes and updated readme

* Updated og image

* Added demo and links to example

* Updated packages

* update name. bump dependencies

* Renamed .env.example to .env.local.example

* Added node_modules to .gitignore

* use recommended config

* enable absolute import/alias support

* remove jsconfig.json

* allow HTML entities in post titles

* add underline to content links

* add basic ul & ol styles

* add code block styles

* add basic text alignment

* add basic image alignment styles

* adjust pre font-size and figcaption

* indent ul,ol lists to line up with grid

* add basic button styles

* add basic file styles

* add basic blockquote style

* add basic audio styles

* add h4 and enhance blockquote styles

* add basic cover block styles

* add basic verse styles

* add basic two-column block styles

* add tags

* add categories

* Only ignore .vercel

The rest is injected by create-next-app

* now → vercel

* npm init → npx

* Wordsmith

* Wordsmith

* Wordsmith

* Wordsmith

* Improve issue link

* Wordsmith

Co-authored-by: Greg Rickaby <greg@gregrickaby.com>
Co-authored-by: Joe Haddad <joe.haddad@zeit.co>
Co-authored-by: Shu Uesugi <shu@chibicode.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Luis Alvarez D 2020-06-01 17:17:20 -05:00 committed by GitHub
parent b124ed2e14
commit ad24a0c855
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1366 additions and 8 deletions

View file

@ -9,6 +9,7 @@ description: Next.js has the preview mode for statically generated pages. You ca
<details open>
<summary><b>Examples</b></summary>
<ul>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-wordpress">WordPress Example</a> (<a href="https://next-blog-wordpress.now.sh">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-datocms">DatoCMS Example</a> (<a href="https://next-blog-datocms.now.sh/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-takeshape">TakeShape Example</a> (<a href="https://next-blog-takeshape.now.sh/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-sanity">Sanity Example</a> (<a href="https://next-blog-sanity.now.sh/">Demo</a>)</li>

View file

@ -9,6 +9,7 @@ description: 'Next.js has 2 pre-rendering modes: Static Generation and Server-si
<details open>
<summary><b>Examples</b></summary>
<ul>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-wordpress">WordPress Example</a> (<a href="https://next-blog-wordpress.now.sh">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/blog-starter">Blog Starter using markdown files</a> (<a href="https://next-blog-starter.now.sh/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-datocms">DatoCMS Example</a> (<a href="https://next-blog-datocms.now.sh/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-takeshape">TakeShape Example</a> (<a href="https://next-blog-takeshape.now.sh/">Demo</a>)</li>

View file

@ -48,6 +48,7 @@ Finally, you can always use **Client-side Rendering** along with Static Generati
<details open>
<summary><b>Examples</b></summary>
<ul>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-wordpress">WordPress Example</a> (<a href="https://next-blog-wordpress.now.sh">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/blog-starter">Blog Starter using markdown files</a> (<a href="https://next-blog-starter.now.sh/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-datocms">DatoCMS Example</a> (<a href="https://next-blog-datocms.now.sh/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-takeshape">TakeShape Example</a> (<a href="https://next-blog-takeshape.now.sh/">Demo</a>)</li>

View file

@ -8,11 +8,12 @@ This example showcases Next.js's [Static Generation](/docs/basic-features/pages.
### Related examples
- [Blog Starter](/examples/blog-starter)
- [WordPress](/examples/cms-wordpress)
- [DatoCMS](/examples/cms-datocms)
- [Prismic](/examples/cms-prismic)
- [TakeShape](/examples/cms-takeshape)
- [Sanity](/examples/cms-sanity)
- [TakeShape](/examples/cms-takeshape)
- [Prismic](/examples/cms-prismic)
- [Blog Starter](/examples/blog-starter)
## How to use

View file

@ -8,10 +8,12 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
### Related examples
- [Blog Starter](/examples/blog-starter)
- [WordPress](/examples/cms-wordpress)
- [Sanity](/examples/cms-sanity)
- [TakeShape](/examples/cms-takeshape)
- [Prismic](/examples/cms-prismic)
- [Contentful](/examples/cms-contentful)
- [Blog Starter](/examples/blog-starter)
## How to use

View file

@ -8,11 +8,12 @@ This example showcases Next.js's [Static Generation](/docs/basic-features/pages.
### Related examples
- [Blog Starter](/examples/blog-starter)
- [WordPress](/examples/cms-wordpress)
- [DatoCMS](/examples/cms-datocms)
- [TakeShape](/examples/cms-takeshape)
- [Sanity](/examples/cms-sanity)
- [TakeShape](/examples/cms-takeshape)
- [Contentful](/examples/cms-contentful)
- [Blog Starter](/examples/blog-starter)
## How to use

View file

@ -8,11 +8,12 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
### Related examples
- [Blog Starter](/examples/blog-starter)
- [WordPress](/examples/cms-wordpress)
- [DatoCMS](/examples/cms-datocms)
- [TakeShape](/examples/cms-takeshape)
- [Prismic](/examples/cms-prismic)
- [Contentful](/examples/cms-contentful)
- [Blog Starter](/examples/blog-starter)
## How to use

View file

@ -8,11 +8,12 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
### Related examples
- [Blog Starter](/examples/blog-starter)
- [WordPress](/examples/cms-wordpress)
- [DatoCMS](/examples/cms-datocms)
- [Sanity](/examples/cms-sanity)
- [Prismic](/examples/cms-prismic)
- [Contentful](/examples/cms-contentful)
- [Blog Starter](/examples/blog-starter)
## How to use

View file

@ -0,0 +1,5 @@
NEXT_EXAMPLE_CMS_WORDPRESS_API_URL=
# Only required if you want to enable preview mode
# NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN=
# NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET=

1
examples/cms-wordpress/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.vercel

View file

@ -0,0 +1,206 @@
# A statically generated blog example using Next.js and WordPress
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [WordPress](https://wordpress.org) as the data source.
## Demo
### [https://next-blog-wordpress.now.sh](https://next-blog-wordpress.now.sh)
### Related examples
- [DatoCMS](/examples/cms-datocms)
- [Sanity](/examples/cms-sanity)
- [TakeShape](/examples/cms-takeshape)
- [Prismic](/examples/cms-prismic)
- [Contentful](/examples/cms-contentful)
- [Blog Starter](/examples/blog-starter)
## 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
npx create-next-app --example cms-wordpress cms-wordpress-app
# or
yarn create next-app --example cms-wordpress cms-wordpress-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-wordpress
cd cms-wordpress
```
## Configuration
### Step 1. Prepare your WordPress site
First, you need a WordPress site. There are many solutions for WordPress hosting, such as [WP Engine](https://wpengine.com/) and [WordPress.com](https://wordpress.com/).
Once the site is ready, you'll need to install the [WPGraphQL](https://www.wpgraphql.com/) plugin. It will add GraphQL API to your WordPress site, which we'll use to query the posts. Follow these steps to install it:
- Download the [WPGraphQL repo](https://github.com/wp-graphql/wp-graphql) as a ZIP archive.
- Inside your WordPress admin, go to **Plugins** and then click **Add New**.
![Add new plugin](./docs/plugins-add-new.png)
- Click the **Upload Plugin** button at the top of the page and upload the WPGraphQL plugin.
![Upload new plugin](./docs/plugins-upload-new.png)
- Once the plugin has been added, activate it from either the **Activate Plugin** button displayed after uploading or from the **Plugins** page.
![WPGraphQL installed](./docs/plugin-installed.png)
#### Optional: Add WPGraphiQL
The [WPGraphiQL](https://github.com/wp-graphql/wp-graphiql) plugin gives you access to a GraphQL IDE directly from your WordPress Admin, allowing you to inspect and play around with the GraphQL API.
The process to add WPGraphiQL is the same as the one for WPGraphQL: Go to the [WPGraphiQL repo](https://github.com/wp-graphql/wp-graphiql), download the ZIP archive, and install it as a plugin in your WordPress site. Once that's done you should be able to access the GraphiQL page in the admin:
![WPGraphiQL page](./docs/wp-graphiql.png)
### Step 2. Populate Content
Inside your WordPress admin, go to **Posts** and start adding new posts:
- We recommend creating at least **2 posts**
- Use dummy data for the content
- Pick an author from your WordPress users
- Add a **Featured Image**. You can download one from [Unsplash](https://unsplash.com/)
- Fill the **Excerpt** field
![New post](./docs/new-post.png)
When youre done, make sure to **Publish** the posts.
> **Note:** Only **published** posts and public fields will be rendered by the app unless [Preview Mode](/docs/advanced-features/preview-mode.md) is enabled.
### 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
```
Then open `.env.local` and set `NEXT_EXAMPLE_CMS_WORDPRESS_API_URL` to be the URL to your GraphQL endpoint in WordPress. For example: `https://myapp.wpengine.com/graphql`.
Your `.env.local` file should look like this:
```bash
NEXT_EXAMPLE_CMS_WORDPRESS_API_URL=...
# Only required if you want to enable preview mode
# NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN=
# NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET=
```
### 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. Add authentication for Preview Mode (Optional)
**This step is optional.** By default, the blog will work with public posts from your WordPress site. Private content such as unpublished posts and private fields cannot be retrieved. To have access to unpublished posts you'll need to set up authentication.
To add [authentication to WPGraphQL](https://docs.wpgraphql.com/guides/authentication-and-authorization/), first you need to add the [WPGraphQL JWT plugin](https://github.com/wp-graphql/wp-graphql-jwt-authentication) to your Wordpress Admin following the same process you used to add the WPGraphQL plugin.
> Adding the WPGraphQL JWT plugin will disable your GraphQL API until you add a JWT secret ([GitHub issue](https://github.com/wp-graphql/wp-graphql-jwt-authentication/issues/91)).
Once that's done, you'll need to access the WordPress filesystem to add the secret required to validate JWT tokens. We recommend using SFTP — the instructions vary depending on your hosting provider. For example:
- [SFTP guide for WP Engine](https://wpengine.com/support/sftp/)
- [SFTP guide for WordPress.com](https://wordpress.com/support/sftp/)
Once you have SFTP access, open `wp-config.php` and add a secret for your JWT:
```php
define( 'GRAPHQL_JWT_AUTH_SECRET_KEY', 'YOUR_STRONG_SECRET' );
```
> You can read more about this in the documentation for [WPGraphQL JWT Authentication](https://docs.wpgraphql.com/extensions/wpgraphql-jwt-authentication/).
Now, you need to get a **refresh token** to make authenticated requests with GraphQL. Make the following GraphQL mutation to your WordPress site from the GraphQL IDE (See notes about WPGraphiQL from earlier). Replace `your_username` with the **username** of a user with the `Administrator` role, and `your_password` with the user's password.
```graphql
mutation Login {
login(
input: {
clientMutationId: "uniqueId"
password: "your_password"
username: "your_username"
}
) {
refreshToken
}
}
```
Copy the `refreshToken` returned by the mutation, then open `.env.local`, and make the following changes:
- Uncomment `NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN` and set it to be the `refreshToken` you just received.
- Uncomment `NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET` and set it to be any random string (ideally URL friendly).
Your `.env.local` file should look like this:
```bash
NEXT_EXAMPLE_CMS_WORDPRESS_API_URL=...
# Only required if you want to enable preview mode
NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN=...
NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET=...
```
**Important:** Restart your Next.js server to update the environment variables.
### Step 6. Try preview mode
On your WordPress admin, create a new post like before, but **do not publish** it.
Now, if you go to `http://localhost:3000`, you wont see the post. However, if you enable **Preview Mode**, you'll be able to see the change ([Documentation](/docs/advanced-features/preview-mode.md)).
To enable Preview Mode, go to this URL:
```
http://localhost:3000/api/preview?secret=<secret>&id=<id>
```
- `<secret>` should be the string you entered for `NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET`.
- `<id>` should be the post's `databaseId` field, which is the integer that you usually see in the URL (`?post=18` → 18).
- Alternatively, you can use `<slug>` instead of `<id>`. `<slug>` is generated based on the title.
You should now be able to see this post. To exit Preview Mode, you can click on **Click here to exit preview mode** at the top.
### Step 7. 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 **Vercel Secrets** using [Vercel CLI](https://vercel.com/download) ([Documentation](https://vercel.com/docs/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 the values with the corresponding strings in `.env.local`:
```bash
vercel secrets add next_example_cms_wordpress_api_url <NEXT_EXAMPLE_CMS_WORDPRESS_API_URL>
# If you added authentication for preview mode:
vercel secrets add next_example_cms_wordpress_auth_refresh_token <NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN>
vercel secrets add next_example_cms_wordpress_preview_secret <NEXT_EXAMPLE_CMS_WORDPRESS_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.

View 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 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>
)
}

View file

@ -0,0 +1,17 @@
export default function Avatar({ author }) {
const name =
author.firstName && author.lastName
? `${author.firstName} ${author.lastName}`
: author.name
return (
<div className="flex items-center">
<img
src={author.avatar.url}
className="w-12 h-12 rounded-full mr-4"
alt={name}
/>
<div className="text-xl font-bold">{name}</div>
</div>
)
}

View file

@ -0,0 +1,16 @@
export default function Categories({ categories }) {
return (
<span className="ml-1">
under
{categories.edges.length > 0 ? (
categories.edges.map((category, index) => (
<span key={index} className="ml-1">
{category.node.name}
</span>
))
) : (
<span className="ml-1">{categories.edges.node.name}</span>
)}
</span>
)
}

View file

@ -0,0 +1,3 @@
export default function Container({ children }) {
return <div className="container mx-auto px-5">{children}</div>
}

View file

@ -0,0 +1,24 @@
import cn from 'classnames'
import Link from 'next/link'
export default function CoverImage({ title, coverImage, slug }) {
const image = (
<img
src={coverImage?.sourceUrl}
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>
)
}

View 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>
}

View 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>
)
}

View 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>
)
}

View file

@ -0,0 +1,43 @@
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 title={title} coverImage={coverImage} slug={slug} />
</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"
dangerouslySetInnerHTML={{ __html: title }}
/>
</Link>
</h3>
<div className="mb-4 md:mb-0 text-lg">
<Date dateString={date} />
</div>
</div>
<div>
<div
className="text-lg leading-relaxed mb-4"
dangerouslySetInnerHTML={{ __html: excerpt }}
/>
<Avatar author={author} />
</div>
</div>
</section>
)
}

View 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>
)
}

View 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 />
</>
)
}

View 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>
)
}

View 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(({ node }) => (
<PostPreview
key={node.slug}
title={node.title}
coverImage={node.featuredImage}
date={node.date}
author={node.author}
slug={node.slug}
excerpt={node.excerpt}
/>
))}
</div>
</section>
)
}

View file

@ -0,0 +1,12 @@
import styles from './post-body.module.css'
export default function PostBody({ content }) {
return (
<div className="max-w-2xl mx-auto">
<div
className={styles.content}
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
)
}

View file

@ -0,0 +1,76 @@
.content {
@apply text-lg leading-relaxed;
}
.content p,
.content ul,
.content ol,
.content blockquote {
@apply my-6;
}
.content a {
@apply underline;
}
.content ul,
.content ol {
@apply pl-4;
}
.content ul {
@apply list-disc;
}
.content ol {
@apply list-decimal;
}
.content ul > li > ul,
.content ol > li > ol {
@apply my-0 ml-4;
}
.content ul > li > ul {
list-style: circle;
}
.content h2 {
@apply text-3xl mt-12 mb-4 leading-snug;
}
.content h3 {
@apply text-2xl mt-8 mb-4 leading-snug;
}
.content h4 {
@apply text-xl mt-6 mb-4 leading-snug;
}
.content pre {
@apply whitespace-pre overflow-x-auto p-4 text-sm leading-tight border border-gray-400 bg-gray-100;
}
.content code {
@apply text-sm;
}
.content figcaption {
@apply text-center text-sm;
}
.content blockquote {
@apply border-l-4 border-gray-500 bg-gray-200 italic ml-0 py-4 px-6;
}
.content blockquote p {
@apply mt-0;
}
.content blockquote cite {
@apply not-italic;
}
.content audio {
@apply w-full;
}

View file

@ -0,0 +1,34 @@
import Avatar from '../components/avatar'
import Date from '../components/date'
import CoverImage from '../components/cover-image'
import PostTitle from '../components/post-title'
import Categories from '../components/categories'
export default function PostHeader({
title,
coverImage,
date,
author,
categories,
}) {
return (
<>
<PostTitle>{title}</PostTitle>
<div className="hidden md:block md:mb-12">
<Avatar author={author} />
</div>
<div className="mb-8 md:mb-16 -mx-5 sm:mx-0">
<CoverImage title={title} coverImage={coverImage} />
</div>
<div className="max-w-2xl mx-auto">
<div className="block md:hidden mb-6">
<Avatar author={author} />
</div>
<div className="mb-6 text-lg">
Posted <Date dateString={date} />
<Categories categories={categories} />
</div>
</div>
</>
)
}

View file

@ -0,0 +1,37 @@
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 title={title} coverImage={coverImage} slug={slug} />
</div>
<h3 className="text-3xl mb-3 leading-snug">
<Link as={`/posts/${slug}`} href="/posts/[slug]">
<a
className="hover:underline"
dangerouslySetInnerHTML={{ __html: title }}
></a>
</Link>
</h3>
<div className="text-lg mb-4">
<Date dateString={date} />
</div>
<div
className="text-lg leading-relaxed mb-4"
dangerouslySetInnerHTML={{ __html: excerpt }}
/>
<Avatar author={author} />
</div>
)
}

View file

@ -0,0 +1,8 @@
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"
dangerouslySetInnerHTML={{ __html: children }}
/>
)
}

View file

@ -0,0 +1,3 @@
export default function SectionSeparator() {
return <hr className="border-accent-2 mt-28 mb-24" />
}

View file

@ -0,0 +1,14 @@
export default function Tags({ tags }) {
return (
<div className="max-w-2xl mx-auto">
<p className="mt-8 text-lg font-bold">
Tagged
{tags.edges.map((tag, index) => (
<span key={index} className="ml-4 font-normal">
{tag.node.name}
</span>
))}
</p>
</div>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View file

@ -0,0 +1,201 @@
const API_URL = process.env.NEXT_EXAMPLE_CMS_WORDPRESS_API_URL
async function fetchAPI(query, { variables } = {}) {
const headers = { 'Content-Type': 'application/json' }
if (process.env.NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN) {
headers[
'Authorization'
] = `Bearer ${process.env.NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN}`
}
const res = await fetch(API_URL, {
method: 'POST',
headers,
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
return json.data
}
export async function getPreviewPost(id, idType = 'DATABASE_ID') {
const data = await fetchAPI(
`
query PreviewPost($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
databaseId
slug
status
}
}`,
{
variables: { id, idType },
}
)
return data.post
}
export async function getAllPostsWithSlug() {
const data = await fetchAPI(`
{
posts(first: 10000) {
edges {
node {
slug
}
}
}
}
`)
return data?.posts
}
export async function getAllPostsForHome(preview) {
const data = await fetchAPI(
`
query AllPosts {
posts(first: 20, where: { orderby: { field: DATE, order: DESC } }) {
edges {
node {
title
excerpt
slug
date
featuredImage {
sourceUrl
}
author {
name
firstName
lastName
avatar {
url
}
}
}
}
}
}
`,
{
variables: {
onlyEnabled: !preview,
preview,
},
}
)
return data?.posts
}
export async function getPostAndMorePosts(slug, preview, previewData) {
const postPreview = preview && previewData?.post
// The slug may be the id of an unpublished post
const isId = Number.isInteger(Number(slug))
const isSamePost = isId
? Number(slug) === postPreview.id
: slug === postPreview.slug
const isDraft = isSamePost && postPreview?.status === 'draft'
const isRevision = isSamePost && postPreview?.status === 'publish'
const data = await fetchAPI(
`
fragment AuthorFields on User {
name
firstName
lastName
avatar {
url
}
}
fragment PostFields on Post {
title
excerpt
slug
date
featuredImage {
sourceUrl
}
author {
...AuthorFields
}
categories {
edges {
node {
name
}
}
}
tags {
edges {
node {
name
}
}
}
}
query PostBySlug($id: ID!, $idType: PostIdType!) {
post(id: $id, idType: $idType) {
...PostFields
content
${
// Only some of the fields of a revision are considered as there are some inconsistencies
isRevision
? `
revisions(first: 1, where: { orderby: { field: MODIFIED, order: ASC } }) {
edges {
node {
title
excerpt
content
author {
...AuthorFields
}
}
}
}
`
: ''
}
}
posts(first: 3, where: { orderby: { field: DATE, order: DESC } }) {
edges {
node {
...PostFields
}
}
}
}
`,
{
variables: {
id: isDraft ? postPreview.id : slug,
idType: isDraft ? 'DATABASE_ID' : 'SLUG',
},
}
)
// Draft posts may not have an slug
if (isDraft) data.post.slug = postPreview.id
// Apply a revision (changes in a published post)
if (isRevision && data.post.revisions) {
const revision = data.post.revisions.edges[0]?.node
if (revision) Object.assign(data.post, revision)
delete data.post.revisions
}
// Filter out the main post
data.posts.edges = data.posts.edges.filter(({ node }) => node.slug !== slug)
// If there are still 3 posts, remove the last one
if (data.posts.edges.length > 2) data.posts.edges.pop()
return data
}

View file

@ -0,0 +1,5 @@
export const EXAMPLE_PATH = 'cms-wordpress'
export const CMS_NAME = 'WordPress'
export const CMS_URL = 'https://wordpress.org'
export const HOME_OG_IMAGE_URL =
'https://og-image.now.sh/Next.js%20Blog%20Example%20with%20**WordPress**.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%2C%253C%253Fxml+version%3D%271.0%27+encoding%3D%27UTF-8%27%253F%253E%253Csvg+preserveAspectRatio%3D%27xMidYMid%27+version%3D%271.1%27+viewBox%3D%270+0+256+255%27+xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%253E%253Cg+fill%3D%27%2523464342%27%253E%253Cpath+d%3D%27m18.124+127.5c0+43.295+25.161+80.711+61.646+98.441l-52.176-142.96c-6.0691+13.603-9.4699+28.657-9.4699+44.515zm183.22-5.5196c0-13.518-4.8557-22.88-9.0204-30.166-5.5446-9.01-10.742-16.64-10.742-25.65+0-10.055+7.6259-19.414+18.367-19.414+0.48494+0+0.94491+0.060358+1.4174+0.087415-19.46-17.828-45.387-28.714-73.863-28.714-38.213+0-71.832+19.606-91.39+49.302+2.5662+0.077008+4.9847+0.13112+7.039+0.13112+11.441+0+29.151-1.3882+29.151-1.3882+5.8963-0.34758+6.5915+8.3127+0.7014+9.01+0+0-5.9255+0.69724-12.519+1.0427l39.832+118.48+23.937-71.79-17.042-46.692c-5.8901-0.3455-11.47-1.0427-11.47-1.0427-5.8942-0.3455-5.2033-9.3575+0.69099-9.01+0+0+18.064+1.3882+28.811+1.3882+11.439+0+29.151-1.3882+29.151-1.3882+5.9005-0.34758+6.5936+8.3127+0.7014+9.01+0+0-5.938+0.69724-12.519+1.0427l39.528+117.58+10.91-36.458c4.7287-15.129+8.3273-25.995+8.3273-35.359zm-71.921+15.087l-32.818+95.363c9.7988+2.8805+20.162+4.4561+30.899+4.4561+12.738+0+24.953-2.202+36.323-6.2002-0.29346-0.46829-0.55987-0.96572-0.77841-1.5069l-33.625-92.112zm94.058-62.046c0.47037+3.4841+0.73678+7.2242+0.73678+11.247+0+11.1-2.073+23.577-8.3169+39.178l-33.411+96.599c32.518-18.963+54.391-54.193+54.391-94.545+0.002081-19.017-4.8557-36.899-13.399-52.48zm-95.977-75.023c-70.304+0-127.5+57.196-127.5+127.5+0+70.313+57.2+127.51+127.5+127.51+70.302+0+127.51-57.194+127.51-127.51-0.002082-70.304-57.209-127.5-127.51-127.5zm0+249.16c-67.08+0-121.66-54.578-121.66-121.66+0-67.08+54.576-121.65+121.66-121.65+67.078+0+121.65+54.574+121.65+121.65+0+67.084-54.574+121.66-121.65+121.66z%27%2F%253E%253C%2Fg%253E%253C%2Fsvg%253E'

View file

@ -0,0 +1,21 @@
{
"name": "cms-wordpress",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"classnames": "2.2.6",
"date-fns": "2.14.0",
"next": "latest",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"postcss-flexbugs-fixes": "4.2.1",
"postcss-preset-env": "^6.7.0",
"tailwindcss": "^1.4.6"
}
}

View file

@ -0,0 +1,7 @@
import '../styles/index.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp

View 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>
)
}
}

View file

@ -0,0 +1,8 @@
export default async function exit(_, 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()
}

View file

@ -0,0 +1,37 @@
import { getPreviewPost } from '../../lib/api'
export default async function preview(req, res) {
const { secret, id, slug } = req.query
// Check the secret and next parameters
// This secret should only be known by this API route
if (
!process.env.NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET ||
secret !== process.env.NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET ||
(!id && !slug)
) {
return res.status(401).json({ message: 'Invalid token' })
}
// Fetch WordPress to check if the provided `id` or `slug` exists
const post = await getPreviewPost(id || slug, id ? 'DATABASE_ID' : 'SLUG')
// If the post doesn't exist prevent preview mode from being enabled
if (!post) {
return res.status(401).json({ message: 'Post not found' })
}
// Enable Preview Mode by setting the cookies
res.setPreviewData({
post: {
id: post.databaseId,
slug: post.slug,
status: post.status,
},
})
// 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 || post.databaseId}` })
res.end()
}

View file

@ -0,0 +1,44 @@
import Head from 'next/head'
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 { CMS_NAME } from '../lib/constants'
export default function Index({ allPosts: { edges }, preview }) {
const heroPost = edges[0]?.node
const morePosts = edges.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.featuredImage}
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: { allPosts, preview },
}
}

View file

@ -0,0 +1,83 @@
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/api'
import PostTitle from '../../components/post-title'
import Head from 'next/head'
import { CMS_NAME } from '../../lib/constants'
import Tags from '../../components/tags'
export default function Post({ post, posts, preview }) {
const router = useRouter()
const morePosts = posts?.edges
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.featuredImage?.sourceUrl}
/>
</Head>
<PostHeader
title={post.title}
coverImage={post.featuredImage}
date={post.date}
author={post.author}
categories={post.categories}
/>
<PostBody content={post.content} />
<footer>
{post.tags.edges.length > 0 && <Tags tags={post.tags} />}
</footer>
</article>
<SectionSeparator />
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
</>
)}
</Container>
</Layout>
)
}
export async function getStaticProps({ params, preview = false, previewData }) {
const data = await getPostAndMorePosts(params.slug, preview, previewData)
return {
props: {
preview,
post: data.post,
posts: data.posts,
},
}
}
export async function getStaticPaths() {
const allPosts = await getAllPostsWithSlug()
return {
paths: allPosts.edges.map(({ node }) => `/posts/${node.slug}`) || [],
fallback: true,
}
}

View 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,
},
},
],
],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View 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

View 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"
}

View file

@ -0,0 +1,104 @@
@tailwind base;
/* Write your own custom base styles here */
/* Start purging... */
@tailwind components;
/* Stop purging. */
/* Write you own custom component styles here */
/* Start purging... */
@tailwind utilities;
/* Stop purging. */
/* Your own custom utilities */
.has-text-align-left {
@apply text-left;
}
.has-text-align-center {
@apply text-center;
}
.has-text-align-right {
@apply text-right;
}
.has-large-font-size {
@apply text-4xl;
}
.alignfull {
@apply w-screen relative;
left: 50%;
margin-left: -50vw;
margin-right: -50vw;
max-width: 100vw;
right: 50%;
}
.wp-block-image img {
@apply max-w-full mt-2;
}
.wp-block-image.aligncenter {
@apply text-center;
}
.wp-block-image.alignfull img,
.wp-block-image.alignwide img {
@apply w-full;
}
.wp-block-image .alignleft,
.wp-block-image .alignright,
.wp-block-image .aligncenter,
.wp-block-image.is-resized {
@apply table ml-0 mr-0;
}
.wp-block-image .alignleft > figcaption,
.wp-block-image .alignright > figcaption,
.wp-block-image .aligncenter > figcaption,
.wp-block-image.is-resized > figcaption {
@apply table-caption;
caption-side: bottom;
}
.wp-block-image .alignleft {
@apply float-left mr-4;
}
.wp-block-image .alignright {
@apply float-right ml-4;
}
.wp-block-image .aligncenter {
@apply m-auto;
}
.wp-block-button a,
.wp-block-file a.wp-block-file__button {
@apply bg-blue-500 text-white no-underline py-2 px-4;
}
.wp-block-button a:hover,
.wp-block-file a.wp-block-file__button:hover {
@apply bg-blue-400 cursor-pointer;
}
.wp-block-file a:first-of-type {
@apply mr-4;
}
.wp-block-cover {
@apply flex flex-wrap justify-center items-center bg-cover bg-center overflow-hidden;
min-height: 430px;
}
.wp-block-verse {
@apply font-sans;
}
.wp-block-media-text {
@apply grid grid-cols-2 gap-4;
}

View 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)',
},
},
},
}

View file

@ -0,0 +1,13 @@
{
"env": {
"NEXT_EXAMPLE_CMS_WORDPRESS_API_URL": "@next_example_cms_wordpress_api_url",
"NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN": "@next_example_cms_wordpress_auth_refresh_token",
"NEXT_EXAMPLE_CMS_WORDPRESS_PREVIEW_SECRET": "@next_example_cms_wordpress_preview_secret"
},
"build": {
"env": {
"NEXT_EXAMPLE_CMS_WORDPRESS_API_URL": "@next_example_cms_wordpress_api_url",
"NEXT_EXAMPLE_CMS_WORDPRESS_AUTH_REFRESH_TOKEN": "@next_example_cms_wordpress_auth_refresh_token"
}
}
}