Skip to content

Commit d5837e0

Browse files
chore(examples): add webiny cms example (vercel#41159)
## 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/examples/adding-examples.md) Co-authored-by: Balázs Orbán <[email protected]>
1 parent 45bed96 commit d5837e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1213
-0
lines changed

Diff for: examples/cms-webiny/.env.local.example

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
PREVIEW_API_SECRET=
2+
WEBINY_API_SECRET=
3+
NEXT_PUBLIC_WEBINY_API_URL=
4+
NEXT_PUBLIC_WEBINY_PREVIEW_API_URL=

Diff for: examples/cms-webiny/.gitignore

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env*.local
29+
30+
# vercel
31+
.vercel
32+
33+
# typescript
34+
*.tsbuildinfo
35+
next-env.d.ts

Diff for: examples/cms-webiny/README.md

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# A statically generated blog example using Next.js and Webiny
2+
3+
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Webiny](https://webiny.com/) as the data source.
4+
5+
## Demo
6+
7+
[https://webiny-headlesscms-nextjs-example.vercel.app/](https://webiny-headlesscms-nextjs-example.vercel.app/)
8+
9+
## Deploy your own
10+
11+
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
12+
13+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-webiny&project-name=cms-webiny&repository-name=cms-webiny&env=PREVIEW_API_SECRET,WEBINY_API_SECRET,NEXT_PUBLIC_WEBINY_API_URL,NEXT_PUBLIC_WEBINY_PREVIEW_API_URL&envDescription=Required%20to%20connect%20the%20app%20with%20Webiny&envLink=https://vercel.link/cms-webiny-env)
14+
15+
### Related examples
16+
17+
- [WordPress](/examples/cms-wordpress)
18+
- [DatoCMS](/examples/cms-datocms)
19+
- [Sanity](/examples/cms-sanity)
20+
- [TakeShape](/examples/cms-takeshape)
21+
- [Prismic](/examples/cms-prismic)
22+
- [Contentful](/examples/cms-contentful)
23+
- [Strapi](/examples/cms-strapi)
24+
- [Agility CMS](/examples/cms-agilitycms)
25+
- [Cosmic](/examples/cms-cosmic)
26+
- [ButterCMS](/examples/cms-buttercms)
27+
- [Storyblok](/examples/cms-storyblok)
28+
- [GraphCMS](/examples/cms-graphcms)
29+
- [Kontent](/examples/cms-kontent)
30+
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
31+
- [Builder.io](/examples/cms-builder-io)
32+
33+
## How to use
34+
35+
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:
36+
37+
```bash
38+
npx create-next-app --example cms-webiny cms-webiny-app
39+
```
40+
41+
```bash
42+
yarn create next-app --example cms-webiny cms-webiny-app
43+
```
44+
45+
```bash
46+
pnpm create next-app --example cms-webiny cms-webiny-app
47+
```
48+
49+
### Step 1. Set up a Webiny project
50+
51+
Follow the [Webiny docs](https://www.webiny.com/docs/tutorials/install-webiny) to install a Webiny project on your cloud hosting provider. Because Webiny is a distributed system we don't run it locally. This also means you don't need to worry about setting up Docker, or installing databases and drivers on your local machine for Postgres, MongoDB or similar. The cloud takes care of that for you.
52+
53+
If you get stuck or have any questions, please [join the community](http://webiny-community.slack.com 'Webiny slack channel') and reach out for some help.
54+
55+
Once you have an app up and running click into the "HeadlessCMS" app in the sidebar, click on _models_ and add the following models and fields:
56+
57+
#### Authors
58+
59+
- A `text` field with the value "name"
60+
- A `text` field with the value "slug" (optionally add a validator using this regex which will make sure you have valid urls: `^(?!.*--)[a-z0-9\-]+$`)
61+
- a `files` field with the value "picture"
62+
63+
#### Posts
64+
65+
- A `text` field with the value "title"
66+
- A `text` field with the value "slug" (optionally use the regex above as a validator)
67+
- A `files` field with the value "featured image"
68+
- A `rich text` field with the value "body"
69+
- A `reference` field with the value "Author"
70+
71+
Next, choose **API Keys** in the sidebar. Add an API key with any name and description. Select "Headless CMS" and choose a Custom access level for all content model groups with the values `read` and `preview`. Save the API token and the token itself will be revealed.
72+
73+
You will be able to use the same API token for both published and draft posts.
74+
75+
### Step 2. Set up environment variables
76+
77+
Copy the `.env.local.example` file to `.env.local`, then set the variables as follows:
78+
79+
- `PREVIEW_API_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).
80+
- WEBINY_API_SECRET this will be your security token generated in Webiny
81+
- You can find the values for `NEXT_PUBLIC_WEBINY_API_URL` and `NEXT_PUBLIC_WEBINY_PREVIEW_API_URL` two ways: From your local Webiny project root, run `yarn webiny info`, alternatively go to **API Playground** in the sidebar. At the top of the GraphQL explorer are four tabs, one for each of our APIs, and you'll see both the Read API and the Preview API on those tabs. The URL for your environment is just below the tab. ([More info here if you get stuck](https://www.webiny.com/docs/headless-cms/basics/graphql-api))
82+
83+
### Step 3. Run Next.js in development mode
84+
85+
Inside the Next.js app directory, run:
86+
87+
```bash
88+
npm install
89+
npm run dev
90+
91+
# or
92+
93+
yarn install
94+
yarn dev
95+
```
96+
97+
Your blog should be up and running on [http://localhost:3000](http://localhost:3000)!
98+
99+
The best place to debug is inside the `fetchAPI` function in `lib/api.js`. If you need help, you can post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
100+
101+
### Step 4. Try preview mode
102+
103+
If you go to the `/posts/draft` page on localhost, you won't see this post because it’s not published. However, if you use the **Preview Mode**, you'll be able to see the change ([Documentation](https://nextjs.org/docs/advanced-features/preview-mode)).
104+
105+
To enable the Preview Mode, go to this URL:
106+
107+
```
108+
http://localhost:3000/api/preview?secret=<secret>&slug=draft
109+
```
110+
111+
- `<secret>` should be the string you entered for `PREVIEW_API_SECRET`.
112+
- `<slug>` should be the post's `slug` attribute.
113+
114+
You should now be able to see the draft post. To exit the preview mode, you can click **Click here to exit preview mode** at the top.
115+
116+
To add more preview pages, create a post and set the **status** as `draft`.
117+
118+
### Step 5. Deploy on Vercel
119+
120+
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)).
121+
122+
#### Deploy Your Local Project
123+
124+
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).
125+
126+
**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
127+
128+
#### Deploy from Our Template
129+
130+
Alternatively, you can deploy using our template by clicking on the Deploy button below.
131+
132+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-webiny&project-name=cms-webiny&repository-name=cms-webiny&env=PREVIEW_API_SECRET,WEBINY_API_SECRET,NEXT_PUBLIC_WEBINY_API_URL,NEXT_PUBLIC_WEBINY_PREVIEW_API_URL&envDescription=Required%20to%20connect%20the%20app%20with%20Webiny&envLink=https://vercel.link/cms-webiny-env)

Diff for: examples/cms-webiny/components/alert.tsx

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import Container from './container'
2+
import cn from 'classnames'
3+
import { EXAMPLE_PATH } from '../lib/constants'
4+
5+
export default function Alert({ preview }) {
6+
return (
7+
<div
8+
className={cn('border-b', {
9+
'bg-accent-7 border-accent-7 text-white': preview,
10+
'bg-accent-1 border-accent-2': !preview,
11+
})}
12+
>
13+
<Container>
14+
<div className="py-2 text-sm text-center">
15+
{preview ? (
16+
<>
17+
This page is a preview.{' '}
18+
<a
19+
href="/api/exit-preview"
20+
className="underline transition-colors duration-200 hover:text-cyan"
21+
>
22+
Click here
23+
</a>{' '}
24+
to exit preview mode.
25+
</>
26+
) : (
27+
<>
28+
The source code for this blog is{' '}
29+
<a
30+
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
31+
className="underline transition-colors duration-200 hover:text-success"
32+
>
33+
available on GitHub
34+
</a>
35+
.
36+
</>
37+
)}
38+
</div>
39+
</Container>
40+
</div>
41+
)
42+
}

Diff for: examples/cms-webiny/components/avatar.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Image from 'next/image'
2+
export default function Avatar({ name, picture }) {
3+
return (
4+
<div className="flex items-center">
5+
<Image
6+
src={picture}
7+
width={48}
8+
height={48}
9+
className="w-12 h-12 mr-4 rounded-full"
10+
alt={name}
11+
/>
12+
<div className="text-xl font-bold">{name}</div>
13+
</div>
14+
)
15+
}

Diff for: examples/cms-webiny/components/container.tsx

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

Diff for: examples/cms-webiny/components/cover-image.tsx

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import cn from 'classnames'
2+
import Link from 'next/link'
3+
import Image from 'next/image'
4+
5+
export type TCoverImage = {
6+
title: string
7+
src: string
8+
slug?: string
9+
height: number
10+
width: number
11+
}
12+
13+
const CoverImage: React.FC<TCoverImage> = ({
14+
title,
15+
src,
16+
slug,
17+
height,
18+
width,
19+
}) => {
20+
const image = (
21+
<Image
22+
src={src}
23+
alt={`Cover Image for ${title}`}
24+
className={cn('shadow-sm', {
25+
'hover:shadow-md transition-shadow duration-200': slug,
26+
})}
27+
layout="responsive"
28+
width={width}
29+
height={height}
30+
/>
31+
)
32+
return (
33+
<div className="sm:mx-0">
34+
{slug ? (
35+
<Link href={`/posts/${slug}`}>
36+
<a aria-label={title}>{image}</a>
37+
</Link>
38+
) : (
39+
image
40+
)}
41+
</div>
42+
)
43+
}
44+
export default CoverImage

Diff for: examples/cms-webiny/components/date-formatter.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { parseISO, format } from 'date-fns'
2+
3+
export default function DateFormatter({ dateString }) {
4+
if (!dateString) {
5+
return null
6+
}
7+
const date = parseISO(dateString)
8+
const formattedDate = format(date, 'LLLL d, yyyy')
9+
return <time dateTime={dateString}>{formattedDate}</time>
10+
}

Diff for: examples/cms-webiny/components/footer.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Container from './container'
2+
import { EXAMPLE_PATH } from '../lib/constants'
3+
4+
export default function Footer() {
5+
return (
6+
<footer className="border-t bg-accent-1 border-accent-2">
7+
<Container>
8+
<div className="flex flex-col items-center py-28 lg:flex-row">
9+
<h3 className="mb-10 text-4xl font-bold leading-tight tracking-tighter text-center lg:text-5xl lg:text-left lg:mb-0 lg:pr-4 lg:w-1/2">
10+
Statically Generated with Next.js.
11+
</h3>
12+
<div className="flex flex-col items-center justify-center lg:flex-row lg:pl-4 lg:w-1/2">
13+
<a
14+
href="https://nextjs.org/docs/basic-features/pages"
15+
className="px-12 py-3 mx-3 mb-6 font-bold text-white transition-colors duration-200 bg-black border border-black hover:bg-white hover:text-black lg:px-8 lg:mb-0"
16+
>
17+
Read Documentation
18+
</a>
19+
<a
20+
href={`https://github.com/vercel/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
21+
className="mx-3 font-bold hover:underline"
22+
>
23+
View on GitHub
24+
</a>
25+
</div>
26+
</div>
27+
</Container>
28+
</footer>
29+
)
30+
}

Diff for: examples/cms-webiny/components/header.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Link from 'next/link'
2+
3+
export default function Header() {
4+
return (
5+
<h2 className="mt-8 mb-20 text-2xl font-bold leading-tight tracking-tight md:text-4xl md:tracking-tighter">
6+
<Link href="/">
7+
<a className="hover:underline">Blog</a>
8+
</Link>
9+
.
10+
</h2>
11+
)
12+
}

Diff for: examples/cms-webiny/components/hero-post.tsx

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Avatar from '../components/avatar'
2+
import DateFormatter from '../components/date-formatter'
3+
import CoverImage from '../components/cover-image'
4+
import Link from 'next/link'
5+
6+
export default function HeroPost({
7+
title,
8+
coverImage,
9+
createdOn,
10+
excerpt,
11+
author,
12+
slug,
13+
}) {
14+
return (
15+
<section>
16+
<div className="mb-8 md:mb-16">
17+
<CoverImage
18+
title={title}
19+
src={coverImage}
20+
slug={slug}
21+
height={620}
22+
width={1240}
23+
/>
24+
</div>
25+
<div className="mb-20 md:grid md:grid-cols-2 md:gap-x-16 lg:gap-x-8 md:mb-28">
26+
<div>
27+
<h3 className="mb-4 text-4xl leading-tight lg:text-6xl">
28+
<Link href={`/posts/${slug}`}>
29+
<a className="hover:underline">{title}</a>
30+
</Link>
31+
</h3>
32+
<div className="mb-4 text-lg md:mb-0">
33+
<DateFormatter dateString={createdOn} />
34+
</div>
35+
</div>
36+
<div>
37+
<p className="mb-4 text-lg leading-relaxed">{excerpt}</p>
38+
<Avatar name={author.name} picture={author.picture} />
39+
</div>
40+
</div>
41+
</section>
42+
)
43+
}

0 commit comments

Comments
 (0)