Add a Plasmic example (#37522)

This adds a new example under `cms-plasmic/`. It serves as a general-purpose example that should be able to work with any Plasmic project, which can be set via environment variables.

## Documentation / Examples

- [x] Make sure the linting passes by running `pnpm lint`
- [x] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples)
This commit is contained in:
Raymond Cheng 2022-06-22 08:19:04 -07:00 committed by GitHub
parent cf3ba271e9
commit ee89517848
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 370 additions and 0 deletions

View file

@ -24,6 +24,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-graphcms">GraphCMS Example</a> (<a href="https://next-blog-graphcms.vercel.app/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-kontent">Kontent Example</a> (<a href="https://next-blog-kontent.vercel.app//">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-umbraco-heartcore">Umbraco Heartcore Example</a> (<a href="https://next-blog-umbraco-heartcore.vercel.app/">Demo</a>)</li>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/cms-plasmic">Plasmic Example</a> (<a href="https://nextjs-plasmic-example.vercel.app/">Demo</a>)</li>
</ul>
</details>

View file

@ -0,0 +1,3 @@
NEXT_PUBLIC_PLASMIC_PROJECT_ID=
NEXT_PUBLIC_PLASMIC_PROJECT_API_TOKEN=
PLASMIC_PREVIEW_SECRET=

32
examples/cms-plasmic/.gitignore vendored Normal file
View file

@ -0,0 +1,32 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel

View file

@ -0,0 +1,110 @@
# A statically generated landing page using Next.js and Plasmic
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Plasmic](https://www.plasmic.app/) as the visual page builder.
You'll get:
- Statically generated pages from your visual designs
- Development server on [preview mode](https://nextjs.org/docs/advanced-features/preview-mode) watches for changes from Plasmic Studio
## Demo
### [https://nextjs-plasmic-example.vercel.app/](https://nextjs-plasmic-example.vercel.app/)
## Deploy your own
Once you have access to the [environment variables you 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/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fcms-plasmic&env=NEXT_PUBLIC_PLASMIC_PROJECT_ID,NEXT_PUBLIC_PLASMIC_PROJECT_API_TOKEN,PLASMIC_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Plasmic&envLink=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fcms-plasmic)
## How to use
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
```bash
npx create-next-app --example cms-plasmic cms-plasmic-app
# or
yarn create next-app --example cms-plasmic cms-plasmic-app
# or
pnpm create next-app --example cms-plasmic cms-plasmic-app
```
## Configuration
### Step 1. Create an account and a project on Plasmic
First, [create an account on Plasmic](https://studio.plasmic.app/).
After creating an account, create a new project.
### Step 2. Gather your project ID and API token
Once you've opened your Plasmic project, you can find the project ID in the URL: `https://studio.plasmic.app/projects/PROJECTID`.
The API token can be found by clicking the Code button in the top bar.
![api token](https://www.plasmic.app/blog/static/images/plasmicflix/08-api-token.png)
### 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 set each variable on `.env.local`:
- `NEXT_PUBLIC_PLASMIC_PROJECT_ID` should be the `projectId` value in step 2.
- `NEXT_PUBLIC_PLASMIC_PROJECT_API_TOKEN` should be the API token gathered in previous step.
- `PLASMIC_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/vercel/next.js/discussions).
### Step 5. Try preview mode
By default, the code is set up to only build published Plasmic projects.
If you want to see changes as you make them in the Plasmic Studio, enter preview mode by opening the following URL:
```
http://localhost:3000/api/preview?secret=PLASMIC_PREVIEW_SECRET&slug=PATH
```
Be sure to replace the secret with the chosen secret in Step 3 and pick a path to preview (e.g. `http://localhost:3000/api/preview?secret=123456&slug=/`)
Now you can make edits in the Studio and see them reflected in the development server live.
You can exit preview mode at any time by going to the following URL:
```
http://localhost:3000/api/exit-preview
```
### 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)).
To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?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.
## Next steps:
With Plasmic, you can enable non-developers on your team to publish pages and content into your website or app.
To learn more about Plasmic, take a look at the following resources:
- [Plasmic Website](https://www.plasmic.app/)
- [Plasmic Documentation](https://docs.plasmic.app/learn/)
- [Plasmic Slack Community](https://www.plasmic.app/slack)
You can check out [the Plasmic GitHub repository](https://github.com/plasmicapp/plasmic) - your feedback and contributions are welcome!

5
examples/cms-plasmic/next-env.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View file

@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig

View file

@ -0,0 +1,19 @@
{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@plasmicapp/loader-nextjs": "1.0.136",
"next": "latest",
"react": "18.1.0",
"react-dom": "18.1.0"
},
"devDependencies": {
"@types/node": "17.0.41",
"@types/react": "18.0.12",
"typescript": "4.7.3"
}
}

View file

@ -0,0 +1,90 @@
import {
PlasmicComponent,
ComponentRenderData,
PlasmicRootProvider,
extractPlasmicQueryData,
} from '@plasmicapp/loader-nextjs'
import type { GetStaticPaths, GetStaticProps } from 'next'
import Error from 'next/error'
import { PLASMIC, PREVIEW_PLASMIC } from '../plasmic-init'
/**
* Use fetchPages() to fetch list of pages that have been created in Plasmic
*/
export const getStaticPaths: GetStaticPaths = async () => {
const pages = await PLASMIC.fetchPages()
return {
paths: pages.map((page) => ({
params: { catchall: page.path.substring(1).split('/') },
})),
fallback: 'blocking',
}
}
/**
* For each page, pre-fetch the data we need to render it
*/
export const getStaticProps: GetStaticProps = async (context) => {
const { catchall } = context.params ?? {}
// Convert the catchall param into a path string
const plasmicPath =
typeof catchall === 'string'
? catchall
: Array.isArray(catchall)
? `/${catchall.join('/')}`
: '/'
const plasmicData = await PLASMIC.maybeFetchComponentData(plasmicPath)
if (!plasmicData) {
// This is some non-Plasmic catch-all page
return {
props: {},
}
}
// This is a path that Plasmic knows about.
// Cache the necessary data fetched for the page.
const queryCache = await extractPlasmicQueryData(
<PlasmicRootProvider loader={PLASMIC} prefetchedData={plasmicData}>
<PlasmicComponent component={plasmicData.entryCompMetas[0].displayName} />
</PlasmicRootProvider>
)
// Pass the data in as props.
return {
props: { plasmicData, queryCache, preview: context.preview ?? null },
// Using incremental static regeneration, will invalidate this page
// after 300s (no deploy webhooks needed)
revalidate: 300,
}
}
/**
* Actually render the page!
*/
export default function CatchallPage(props: {
plasmicData?: ComponentRenderData
queryCache?: Record<string, any>
preview?: boolean
}) {
const { plasmicData, queryCache, preview } = props
if (!plasmicData || plasmicData.entryCompMetas.length === 0) {
return <Error statusCode={404} />
}
const pageMeta = plasmicData.entryCompMetas[0]
return (
// Pass in the data fetched in getStaticProps as prefetchedData
<PlasmicRootProvider
loader={preview ? PREVIEW_PLASMIC : PLASMIC}
prefetchedData={preview ? undefined : plasmicData}
prefetchedQueryData={preview ? undefined : queryCache}
>
{
// plasmicData.entryCompMetas[0].displayName contains the name
// of the component you fetched.
}
<PlasmicComponent component={pageMeta.displayName} />
</PlasmicRootProvider>
)
}

View file

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

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,28 @@
import { PREVIEW_PLASMIC } from '../../plasmic-init'
export default async function preview(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.PLASMIC_PREVIEW_SECRET ||
!req.query.slug
) {
return res.status(401).json({ message: 'Invalid token' })
}
// Check if the page with the given `slug` exists
const pages = await PREVIEW_PLASMIC.fetchPages()
const pageMeta = pages.find((p) => p.path === req.query.slug)
// If the slug doesn't exist prevent preview mode from being enabled
if (!pageMeta) {
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.redirect(pageMeta.path)
}

View file

@ -0,0 +1,26 @@
import { initPlasmicLoader } from '@plasmicapp/loader-nextjs'
const PLASMIC_PROJECT_ID = process.env['NEXT_PUBLIC_PLASMIC_PROJECT_ID']
const PLASMIC_PROJECT_API_TOKEN =
process.env['NEXT_PUBLIC_PLASMIC_PROJECT_API_TOKEN']
const PLASMIC_CONFIG = {
projects: [
{
id: PLASMIC_PROJECT_ID,
token: PLASMIC_PROJECT_API_TOKEN,
},
],
}
export const PLASMIC = initPlasmicLoader({
...PLASMIC_CONFIG,
preview: false,
})
export const PREVIEW_PLASMIC = initPlasmicLoader({
...PLASMIC_CONFIG,
// Fetches the latest revisions, whether or not they were unpublished!
// Disable for production to ensure you render only published changes.
preview: true,
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,16 @@
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}

View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}