[examples] Added blog-starter example using Ghost CMS (#19990)
|
@ -29,6 +29,7 @@ Once you have access to [the environment variables you'll need](#step-15-set-up-
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-2-set-up-e
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -20,6 +20,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## Deploy your own
|
## Deploy your own
|
||||||
|
|
2
examples/cms-ghost/.env.local.example
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
GHOST_API_URL=
|
||||||
|
GHOST_API_KEY=
|
34
examples/cms-ghost/.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# 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*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
86
examples/cms-ghost/README.md
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# A statically generated blog example using Next.js and Ghost
|
||||||
|
|
||||||
|
This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Ghost](https://ghost.org/) as the data source.
|
||||||
|
|
||||||
|
> This boilerplate demonstrates simple usage and best practices. If you are looking for a more feature richt Next.js generator for Ghost including the Casper theme,
|
||||||
|
> check out [next-cms-ghost](https://github.com/styxlab/next-cms-ghost).
|
||||||
|
|
||||||
|
## Deploy your own
|
||||||
|
|
||||||
|
Once you have access to [the environment variables you'll need](#step-2-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||||
|
|
||||||
|
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-ghost&env=ghost_BUCKET_SLUG,ghost_READ_KEY,ghost_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20ghost&envLink=https://vercel.link/cms-ghost-env)
|
||||||
|
|
||||||
|
### Related examples
|
||||||
|
|
||||||
|
- [WordPress](/examples/cms-wordpress)
|
||||||
|
- [DatoCMS](/examples/cms-datocms)
|
||||||
|
- [Sanity](/examples/cms-sanity)
|
||||||
|
- [TakeShape](/examples/cms-takeshape)
|
||||||
|
- [Prismic](/examples/cms-prismic)
|
||||||
|
- [Contentful](/examples/cms-contentful)
|
||||||
|
- [Strapi](/examples/cms-strapi)
|
||||||
|
- [Agility CMS](/examples/cms-agilitycms)
|
||||||
|
- [ButterCMS](/examples/cms-buttercms)
|
||||||
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
|
## 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) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx create-next-app --example cms-ghost cms-ghost-app
|
||||||
|
# or
|
||||||
|
yarn create next-app --example cms-ghost cms-ghost-app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setp 1. Run Next.js in development mode
|
||||||
|
|
||||||
|
To get started, no configuration is needed as this example sources the content from a demo Ghost CMS.
|
||||||
|
|
||||||
|
```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 2. Set up environment variables
|
||||||
|
|
||||||
|
If you already have a Ghost CMS running, you should create new Content API keys in the Ghost Admin panel under Integrations -> Add custom integration.
|
||||||
|
Once your keys are generated, copy them into the environment variables as follows. Your `.env.local` file should look like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GHOST_API_URL=...
|
||||||
|
GHOST_API_KEY=...
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure to use the Content API Key.
|
||||||
|
|
||||||
|
### Step 3. Set up Headless Ghost CMS
|
||||||
|
|
||||||
|
If you do not have access to a Ghost CMS, you need to create one for your own content. The demo Ghost CMS is running on Hetzner Cloud, which is described in [Ghost CMS on Hetzner Cloud](https://www.jamify.org/2020/04/07/ghost-cms-on-hetzner-cloud/). Note that a Ghost install on localhost is not sufficient for public deploys, as the images on your local computer are not accessible from outside.
|
||||||
|
|
||||||
|
### Step 4. Deploy on Vercel
|
||||||
|
|
||||||
|
You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||||
|
|
||||||
|
#### Deploy Your Local Project
|
||||||
|
|
||||||
|
To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example).
|
||||||
|
|
||||||
|
**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
|
||||||
|
|
||||||
|
#### Deploy from Our Template
|
||||||
|
|
||||||
|
Alternatively, you can deploy using our template by clicking on the Deploy button below.
|
||||||
|
|
||||||
|
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/cms-ghost&env=GHOST_API_URL,GHOST_API_KEY&envDescription=Required%20to%20connect%20the%20app%20with%20ghost&envLink=https://vercel.link/cms-ghost-env)
|
42
examples/cms-ghost/components/alert.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import Container from './container'
|
||||||
|
import cn from 'classnames'
|
||||||
|
import { EXAMPLE_PATH } from '@/lib/constants'
|
||||||
|
|
||||||
|
export default function Alert({ preview }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn('border-b', {
|
||||||
|
'bg-accent-7 border-accent-7 text-white': preview,
|
||||||
|
'bg-accent-1 border-accent-2': !preview,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Container>
|
||||||
|
<div className="py-2 text-center text-sm">
|
||||||
|
{preview ? (
|
||||||
|
<>
|
||||||
|
This 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>
|
||||||
|
)
|
||||||
|
}
|
14
examples/cms-ghost/components/avatar.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
export default function Avatar({ name, picture }) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
{picture && (
|
||||||
|
<img
|
||||||
|
src={`${picture}?auto=format,compress,enhance&w=100&h=100`}
|
||||||
|
className="w-12 h-12 rounded-full mr-4"
|
||||||
|
alt={name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="text-xl font-bold">{name}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
3
examples/cms-ghost/components/container.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function Container({ children }) {
|
||||||
|
return <div className="container mx-auto px-5">{children}</div>
|
||||||
|
}
|
29
examples/cms-ghost/components/cover-image.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import cn from 'classnames'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
export default function CoverImage({ title, url, slug, width, height }) {
|
||||||
|
const image = (
|
||||||
|
<Image
|
||||||
|
src={url}
|
||||||
|
alt={`Cover Image for ${title}`}
|
||||||
|
className={cn('shadow-sm', {
|
||||||
|
'hover:shadow-md transition-shadow duration-200': slug,
|
||||||
|
})}
|
||||||
|
layout="responsive"
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div className="sm:mx-0">
|
||||||
|
{slug ? (
|
||||||
|
<Link href={`/posts/${slug}`}>
|
||||||
|
<a aria-label={title}>{image}</a>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
image
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
6
examples/cms-ghost/components/date.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { parseISO, format } from 'date-fns'
|
||||||
|
|
||||||
|
export default function Date({ dateString }) {
|
||||||
|
const date = parseISO(dateString)
|
||||||
|
return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>
|
||||||
|
}
|
30
examples/cms-ghost/components/footer.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import Container from './container'
|
||||||
|
import { EXAMPLE_PATH } from '@/lib/constants'
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="bg-accent-1 border-t border-accent-2">
|
||||||
|
<Container>
|
||||||
|
<div className="py-28 flex flex-col lg:flex-row items-center">
|
||||||
|
<h3 className="text-4xl lg:text-5xl font-bold tracking-tighter leading-tight text-center lg:text-left mb-10 lg:mb-0 lg:pr-4 lg:w-1/2">
|
||||||
|
Statically Generated with Next.js.
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-col lg:flex-row justify-center items-center lg:pl-4 lg:w-1/2">
|
||||||
|
<a
|
||||||
|
href="https://nextjs.org/docs/basic-features/pages"
|
||||||
|
className="mx-3 bg-black hover:bg-white hover:text-black border border-black text-white font-bold py-3 px-12 lg:px-8 duration-200 transition-colors mb-6 lg:mb-0"
|
||||||
|
>
|
||||||
|
Read Documentation
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={`https://github.com/zeit/next.js/tree/canary/examples/${EXAMPLE_PATH}`}
|
||||||
|
className="mx-3 font-bold hover:underline"
|
||||||
|
>
|
||||||
|
View on GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
12
examples/cms-ghost/components/header.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<h2 className="text-2xl md:text-4xl font-bold tracking-tight md:tracking-tighter leading-tight mb-20 mt-8">
|
||||||
|
<Link href="/">
|
||||||
|
<a className="hover:underline">Blog</a>
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</h2>
|
||||||
|
)
|
||||||
|
}
|
43
examples/cms-ghost/components/hero-post.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import Avatar from './avatar'
|
||||||
|
import Date from './date'
|
||||||
|
import CoverImage from './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}
|
||||||
|
url={coverImage}
|
||||||
|
slug={slug}
|
||||||
|
width={2000}
|
||||||
|
height={1216}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
<Avatar name={author.name} picture={author.profile_image} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
28
examples/cms-ghost/components/intro.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { CMS_NAME, CMS_URL } from '@/lib/constants'
|
||||||
|
|
||||||
|
export default function Intro() {
|
||||||
|
return (
|
||||||
|
<section className="flex-col md:flex-row flex items-center md:justify-between mt-16 mb-16 md:mb-12">
|
||||||
|
<h1 className="text-6xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
|
||||||
|
Blog.
|
||||||
|
</h1>
|
||||||
|
<h4 className="text-center md:text-left text-lg mt-5 md:pl-8">
|
||||||
|
A statically generated blog example using{' '}
|
||||||
|
<a
|
||||||
|
href="https://nextjs.org/"
|
||||||
|
className="underline hover:text-success duration-200 transition-colors"
|
||||||
|
>
|
||||||
|
Next.js
|
||||||
|
</a>{' '}
|
||||||
|
and{' '}
|
||||||
|
<a
|
||||||
|
href={CMS_URL}
|
||||||
|
className="underline hover:text-success duration-200 transition-colors"
|
||||||
|
>
|
||||||
|
{CMS_NAME}
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</h4>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
18
examples/cms-ghost/components/layout.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import Alert from './alert'
|
||||||
|
import Footer from './footer'
|
||||||
|
import Meta from './meta'
|
||||||
|
import 'lazysizes'
|
||||||
|
import 'lazysizes/plugins/parent-fit/ls.parent-fit'
|
||||||
|
|
||||||
|
export default function Layout({ preview, children }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Meta />
|
||||||
|
<div className="min-h-screen">
|
||||||
|
<Alert preview={preview} />
|
||||||
|
<main>{children}</main>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
18
examples/cms-ghost/components/markdown-styles.module.css
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.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;
|
||||||
|
}
|
42
examples/cms-ghost/components/meta.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { CMS_NAME, HOME_OG_IMAGE_URL } from '@/lib/constants'
|
||||||
|
|
||||||
|
export default function Meta() {
|
||||||
|
return (
|
||||||
|
<Head>
|
||||||
|
<link
|
||||||
|
rel="apple-touch-icon"
|
||||||
|
sizes="180x180"
|
||||||
|
href="/favicon/apple-touch-icon.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="/favicon/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="16x16"
|
||||||
|
href="/favicon/favicon-16x16.png"
|
||||||
|
/>
|
||||||
|
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||||
|
<link
|
||||||
|
rel="mask-icon"
|
||||||
|
href="/favicon/safari-pinned-tab.svg"
|
||||||
|
color="#000000"
|
||||||
|
/>
|
||||||
|
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||||
|
<meta name="msapplication-TileColor" content="#000000" />
|
||||||
|
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
|
||||||
|
<meta name="theme-color" content="#000" />
|
||||||
|
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content={`A statically generated blog example using Next.js and ${CMS_NAME}.`}
|
||||||
|
/>
|
||||||
|
<meta property="og:image" content={HOME_OG_IMAGE_URL} />
|
||||||
|
</Head>
|
||||||
|
)
|
||||||
|
}
|
24
examples/cms-ghost/components/more-stories.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import PostPreview from './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.feature_image}
|
||||||
|
date={post.published_at}
|
||||||
|
author={post.primary_author}
|
||||||
|
slug={post.slug}
|
||||||
|
excerpt={post.excerpt}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
12
examples/cms-ghost/components/post-body.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import markdownStyles from './markdown-styles.module.css'
|
||||||
|
|
||||||
|
export default function PostBody({ content }) {
|
||||||
|
return (
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<div
|
||||||
|
className={markdownStyles['markdown']}
|
||||||
|
dangerouslySetInnerHTML={{ __html: content }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
26
examples/cms-ghost/components/post-header.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import Avatar from './avatar'
|
||||||
|
import Date from './date'
|
||||||
|
import CoverImage from './cover-image'
|
||||||
|
import PostTitle from './post-title'
|
||||||
|
|
||||||
|
export default function PostHeader({ title, coverImage, date, author }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PostTitle>{title}</PostTitle>
|
||||||
|
<div className="hidden md:block md:mb-12">
|
||||||
|
<Avatar name={author.title} picture={author.profile_image} />
|
||||||
|
</div>
|
||||||
|
<div className="mb-8 md:mb-16 sm:mx-0">
|
||||||
|
<CoverImage title={title} url={coverImage} width={2000} height={1216} />
|
||||||
|
</div>
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<div className="block md:hidden mb-6">
|
||||||
|
<Avatar name={author.name} picture={author.profile_image} />
|
||||||
|
</div>
|
||||||
|
<div className="mb-6 text-lg">
|
||||||
|
<Date dateString={date} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
37
examples/cms-ghost/components/post-preview.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import Avatar from './avatar'
|
||||||
|
import Date from './date'
|
||||||
|
import CoverImage from './cover-image'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
export default function PostPreview({
|
||||||
|
title,
|
||||||
|
coverImage,
|
||||||
|
date,
|
||||||
|
excerpt,
|
||||||
|
author,
|
||||||
|
slug,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<CoverImage
|
||||||
|
slug={slug}
|
||||||
|
title={title}
|
||||||
|
url={coverImage}
|
||||||
|
width={2000}
|
||||||
|
height={1216}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-3xl mb-3 leading-snug">
|
||||||
|
<Link 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>
|
||||||
|
<Avatar name={author.name} picture={author.profile_image} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
7
examples/cms-ghost/components/post-title.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function PostTitle({ children }) {
|
||||||
|
return (
|
||||||
|
<h1 className="text-6xl md:text-7xl lg:text-8xl font-bold tracking-tighter leading-tight md:leading-none mb-12 text-center md:text-left">
|
||||||
|
{children}
|
||||||
|
</h1>
|
||||||
|
)
|
||||||
|
}
|
3
examples/cms-ghost/components/section-separator.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function SectionSeparator() {
|
||||||
|
return <hr className="border-accent-2 mt-28 mb-24" />
|
||||||
|
}
|
10
examples/cms-ghost/jsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/components/*": ["components/*"],
|
||||||
|
"@/lib/*": ["lib/*"],
|
||||||
|
"@/styles/*": ["styles/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
examples/cms-ghost/lib/api.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import GhostContentAPI from '@tryghost/content-api'
|
||||||
|
import { GHOST_API_URL_DEFAULT, GHOST_API_KEY_DEFAULT } from './defaults.js'
|
||||||
|
|
||||||
|
const GHOST_API_URL = process.env.GHOST_API_URL || GHOST_API_URL_DEFAULT
|
||||||
|
const GHOST_API_KEY = process.env.GHOST_API_KEY || GHOST_API_KEY_DEFAULT
|
||||||
|
|
||||||
|
const api = new GhostContentAPI({
|
||||||
|
url: GHOST_API_URL,
|
||||||
|
key: GHOST_API_KEY,
|
||||||
|
version: 'v3',
|
||||||
|
})
|
||||||
|
|
||||||
|
const is404 = (error) => /not found/i.test(error.message)
|
||||||
|
|
||||||
|
export async function getPreviewPostBySlug(slug) {
|
||||||
|
const params = {
|
||||||
|
slug,
|
||||||
|
fields: 'slug',
|
||||||
|
limit: 'all',
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const post = await api.posts.read(params)
|
||||||
|
return post
|
||||||
|
} catch (error) {
|
||||||
|
// Don't throw if an slug doesn't exist
|
||||||
|
if (is404(error)) return
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllPostsWithSlug() {
|
||||||
|
const params = {
|
||||||
|
fields: 'slug',
|
||||||
|
limit: 'all',
|
||||||
|
}
|
||||||
|
const posts = await api.posts.browse(params)
|
||||||
|
return posts
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllPostsForHome(preview) {
|
||||||
|
const params = {
|
||||||
|
limit: 'all',
|
||||||
|
include: 'authors',
|
||||||
|
order: 'published_at DESC',
|
||||||
|
...(preview && { status: 'all' }),
|
||||||
|
}
|
||||||
|
const posts = await api.posts.browse(params)
|
||||||
|
return posts
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPostAndMorePosts(slug, preview) {
|
||||||
|
const singleObjectParams = {
|
||||||
|
slug,
|
||||||
|
include: 'authors',
|
||||||
|
...(preview && { status: 'all' }),
|
||||||
|
}
|
||||||
|
const moreObjectParams = {
|
||||||
|
limit: 3,
|
||||||
|
include: 'authors',
|
||||||
|
...(preview && { status: 'all' }),
|
||||||
|
}
|
||||||
|
const post = await api.posts.read(singleObjectParams).catch((error) => {
|
||||||
|
// Don't throw if an slug doesn't exist
|
||||||
|
if (is404(error)) return
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
const morePosts = (await api.posts.browse(moreObjectParams))
|
||||||
|
?.filter(({ slug }) => post.slug !== slug)
|
||||||
|
.slice(0, 2)
|
||||||
|
|
||||||
|
return {
|
||||||
|
post,
|
||||||
|
morePosts,
|
||||||
|
}
|
||||||
|
}
|
5
examples/cms-ghost/lib/constants.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const EXAMPLE_PATH = 'cms-ghost'
|
||||||
|
export const CMS_NAME = 'Ghost'
|
||||||
|
export const CMS_URL = 'https://ghost.org/'
|
||||||
|
export const HOME_OG_IMAGE_URL =
|
||||||
|
'https://og-image.now.sh/Next.js%20Example%20Blog%20with%20**Ghost**.png?theme=light&md=1&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https%3A%2F%2Fghost.org%2Fimages%2Flogo.svg'
|
9
examples/cms-ghost/lib/defaults.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export const GHOST_API_URL_DEFAULT = Buffer.from(
|
||||||
|
'aHR0cHM6Ly9jbXMuZ290c2J5Lm9yZw==',
|
||||||
|
'base64'
|
||||||
|
).toString('utf-8')
|
||||||
|
|
||||||
|
export const GHOST_API_KEY_DEFAULT = Buffer.from(
|
||||||
|
'Mzg3Zjk1NmVhYTk1MzQ1ZjdiYjQ4NGQwYjg=',
|
||||||
|
'base64'
|
||||||
|
).toString('utf-8')
|
5
examples/cms-ghost/next.config.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
images: {
|
||||||
|
domains: ['static.gotsby.org'],
|
||||||
|
},
|
||||||
|
}
|
25
examples/cms-ghost/package.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "cms-ghost",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tryghost/content-api": "^1.4.13",
|
||||||
|
"classnames": "2.2.6",
|
||||||
|
"date-fns": "2.16.1",
|
||||||
|
"lazysizes": "^5.3.0",
|
||||||
|
"next": "latest",
|
||||||
|
"postcss": "^8.2.4",
|
||||||
|
"react": "^17.0.1",
|
||||||
|
"react-dom": "^17.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"postcss-flexbugs-fixes": "^5.0.2",
|
||||||
|
"postcss-preset-env": "^6.7.0",
|
||||||
|
"tailwindcss": "^2.0.2"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
7
examples/cms-ghost/pages/_app.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import '@/styles/index.css'
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }) {
|
||||||
|
return <Component {...pageProps} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyApp
|
15
examples/cms-ghost/pages/_document.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default class MyDocument extends Document {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Html lang="en">
|
||||||
|
<Head />
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
8
examples/cms-ghost/pages/api/exit-preview.js
Normal 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()
|
||||||
|
}
|
28
examples/cms-ghost/pages/api/preview.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { getPreviewPostBySlug } from '@/lib/api'
|
||||||
|
|
||||||
|
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.GHOST_PREVIEW_SECRET ||
|
||||||
|
!req.query.slug
|
||||||
|
) {
|
||||||
|
return res.status(401).json({ message: 'Invalid token' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the headless CMS to check if the provided `slug` exists
|
||||||
|
const post = await getPreviewPostBySlug(req.query.slug)
|
||||||
|
|
||||||
|
// If the slug doesn't exist prevent preview mode from being enabled
|
||||||
|
if (!post) {
|
||||||
|
return res.status(401).json({ message: 'Invalid slug' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable Preview Mode by setting the cookies
|
||||||
|
res.setPreviewData({})
|
||||||
|
|
||||||
|
// Redirect to the path from the fetched post
|
||||||
|
// We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
|
||||||
|
res.writeHead(307, { Location: `/posts/${post.slug}` })
|
||||||
|
res.end()
|
||||||
|
}
|
43
examples/cms-ghost/pages/index.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import Container from '@/components/container'
|
||||||
|
import MoreStories from '@/components/more-stories'
|
||||||
|
import HeroPost from '@/components/hero-post'
|
||||||
|
import Intro from '@/components/intro'
|
||||||
|
import Layout from '@/components/layout'
|
||||||
|
import { getAllPostsForHome } from '@/lib/api'
|
||||||
|
import Head from 'next/head'
|
||||||
|
import { CMS_NAME } from '@/lib/constants'
|
||||||
|
|
||||||
|
export default function Index({ allPosts }) {
|
||||||
|
const heroPost = allPosts[0]
|
||||||
|
const morePosts = allPosts.slice(1)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout>
|
||||||
|
<Head>
|
||||||
|
<title>Next.js Blog Example with {CMS_NAME}</title>
|
||||||
|
</Head>
|
||||||
|
<Container>
|
||||||
|
<Intro />
|
||||||
|
{heroPost && (
|
||||||
|
<HeroPost
|
||||||
|
title={heroPost.title}
|
||||||
|
coverImage={heroPost.feature_image}
|
||||||
|
date={heroPost.published_at}
|
||||||
|
author={heroPost.primary_author}
|
||||||
|
slug={heroPost.slug}
|
||||||
|
excerpt={heroPost.excerpt}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps({ preview }) {
|
||||||
|
const allPosts = (await getAllPostsForHome(preview)) || []
|
||||||
|
return {
|
||||||
|
props: { allPosts },
|
||||||
|
}
|
||||||
|
}
|
69
examples/cms-ghost/pages/posts/[slug].js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
export default function Post({ post, morePosts, preview }) {
|
||||||
|
const router = useRouter()
|
||||||
|
if (!router.isFallback && !post?.slug) {
|
||||||
|
return <ErrorPage statusCode={404} />
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Layout preview={preview}>
|
||||||
|
<Container>
|
||||||
|
<Header />
|
||||||
|
{router.isFallback ? (
|
||||||
|
<PostTitle>Loading…</PostTitle>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<article>
|
||||||
|
<Head>
|
||||||
|
<title>
|
||||||
|
{post.title} | Next.js Blog Example with {CMS_NAME}
|
||||||
|
</title>
|
||||||
|
<meta property="og:image" content={post.feature_image} />
|
||||||
|
</Head>
|
||||||
|
<PostHeader
|
||||||
|
title={post.title}
|
||||||
|
coverImage={post.feature_image}
|
||||||
|
date={post.published_at}
|
||||||
|
author={post.primary_author}
|
||||||
|
/>
|
||||||
|
<PostBody content={post.html} />
|
||||||
|
</article>
|
||||||
|
<SectionSeparator />
|
||||||
|
{morePosts.length > 0 && <MoreStories posts={morePosts} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps({ params, preview = null }) {
|
||||||
|
const { post, morePosts } = await getPostAndMorePosts(params.slug, preview)
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
preview,
|
||||||
|
post,
|
||||||
|
morePosts: morePosts || [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const allPosts = (await getAllPostsWithSlug()) || []
|
||||||
|
return {
|
||||||
|
paths: allPosts.map((post) => `/posts/${post.slug}`),
|
||||||
|
fallback: true,
|
||||||
|
}
|
||||||
|
}
|
18
examples/cms-ghost/postcss.config.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
'tailwindcss',
|
||||||
|
'postcss-flexbugs-fixes',
|
||||||
|
[
|
||||||
|
'postcss-preset-env',
|
||||||
|
{
|
||||||
|
autoprefixer: {
|
||||||
|
flexbox: 'no-2009',
|
||||||
|
},
|
||||||
|
stage: 3,
|
||||||
|
features: {
|
||||||
|
'custom-properties': false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
}
|
BIN
examples/cms-ghost/public/favicon/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
examples/cms-ghost/public/favicon/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
examples/cms-ghost/public/favicon/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
9
examples/cms-ghost/public/favicon/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/favicons/mstile-150x150.png"/>
|
||||||
|
<TileColor>#000000</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
BIN
examples/cms-ghost/public/favicon/favicon-16x16.png
Normal file
After Width: | Height: | Size: 595 B |
BIN
examples/cms-ghost/public/favicon/favicon-32x32.png
Normal file
After Width: | Height: | Size: 880 B |
BIN
examples/cms-ghost/public/favicon/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
examples/cms-ghost/public/favicon/mstile-150x150.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
33
examples/cms-ghost/public/favicon/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M4785 10234 c-22 -2 -92 -9 -155 -14 -1453 -131 -2814 -915 -3676
|
||||||
|
-2120 -480 -670 -787 -1430 -903 -2235 -41 -281 -46 -364 -46 -745 0 -381 5
|
||||||
|
-464 46 -745 278 -1921 1645 -3535 3499 -4133 332 -107 682 -180 1080 -224
|
||||||
|
155 -17 825 -17 980 0 687 76 1269 246 1843 539 88 45 105 57 93 67 -8 6 -383
|
||||||
|
509 -833 1117 l-818 1105 -1025 1517 c-564 834 -1028 1516 -1032 1516 -4 1 -8
|
||||||
|
-673 -10 -1496 -3 -1441 -4 -1499 -22 -1533 -26 -49 -46 -69 -88 -91 -32 -16
|
||||||
|
-60 -19 -211 -19 l-173 0 -46 29 c-30 19 -52 44 -67 73 l-21 45 2 2005 3 2006
|
||||||
|
31 39 c16 21 50 48 74 61 41 20 57 22 230 22 204 0 238 -8 291 -66 15 -16 570
|
||||||
|
-852 1234 -1859 664 -1007 1572 -2382 2018 -3057 l810 -1227 41 27 c363 236
|
||||||
|
747 572 1051 922 647 743 1064 1649 1204 2615 41 281 46 364 46 745 0 381 -5
|
||||||
|
464 -46 745 -278 1921 -1645 3535 -3499 4133 -327 106 -675 179 -1065 223 -96
|
||||||
|
10 -757 21 -840 13z m2094 -3094 c48 -24 87 -70 101 -118 8 -26 10 -582 8
|
||||||
|
-1835 l-3 -1798 -317 486 -318 486 0 1307 c0 845 4 1320 10 1343 16 56 51 100
|
||||||
|
99 126 41 21 56 23 213 23 148 0 174 -2 207 -20z"/>
|
||||||
|
<path d="M7843 789 c-35 -22 -46 -37 -15 -20 22 13 58 40 52 41 -3 0 -20 -10
|
||||||
|
-37 -21z"/>
|
||||||
|
<path d="M7774 744 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||||
|
<path d="M7724 714 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||||
|
<path d="M7674 684 c-18 -14 -18 -15 4 -4 12 6 22 13 22 15 0 8 -5 6 -26 -11z"/>
|
||||||
|
<path d="M7598 644 c-38 -20 -36 -28 2 -9 17 9 30 18 30 20 0 7 -1 6 -32 -11z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
19
examples/cms-ghost/public/favicon/site.webmanifest
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "Next.js",
|
||||||
|
"short_name": "Next.js",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/favicons/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/favicons/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#000000",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
3
examples/cms-ghost/styles/index.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
33
examples/cms-ghost/tailwind.config.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
module.exports = {
|
||||||
|
purge: ['./components/**/*.js', './pages/**/*.js'],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'accent-1': '#FAFAFA',
|
||||||
|
'accent-2': '#EAEAEA',
|
||||||
|
'accent-7': '#333',
|
||||||
|
success: '#0070f3',
|
||||||
|
cyan: '#79FFE1',
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
28: '7rem',
|
||||||
|
},
|
||||||
|
letterSpacing: {
|
||||||
|
tighter: '-.04em',
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
tight: 1.2,
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
'5xl': '2.5rem',
|
||||||
|
'6xl': '2.75rem',
|
||||||
|
'7xl': '4.5rem',
|
||||||
|
'8xl': '6.25rem',
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
small: '0 5px 10px rgba(0, 0, 0, 0.12)',
|
||||||
|
medium: '0 8px 30px rgba(0, 0, 0, 0.12)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
|
||||||
- [ButterCMS](/examples/cms-buttercms)
|
- [ButterCMS](/examples/cms-buttercms)
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## Deploy your own
|
## Deploy your own
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
|
||||||
- [ButterCMS](/examples/cms-buttercms)
|
- [ButterCMS](/examples/cms-buttercms)
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -25,6 +25,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
|
||||||
- [ButterCMS](/examples/cms-buttercms)
|
- [ButterCMS](/examples/cms-buttercms)
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-4-set-up-e
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-6-set-up-e
|
||||||
- [ButterCMS](/examples/cms-buttercms)
|
- [ButterCMS](/examples/cms-buttercms)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-7-set-up-e
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
|
@ -26,6 +26,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
|
||||||
- [Storyblok](/examples/cms-storyblok)
|
- [Storyblok](/examples/cms-storyblok)
|
||||||
- [GraphCMS](/examples/cms-graphcms)
|
- [GraphCMS](/examples/cms-graphcms)
|
||||||
- [Kontent](/examples/cms-kontent)
|
- [Kontent](/examples/cms-kontent)
|
||||||
|
- [Ghost](/examples/cms-ghost)
|
||||||
- [Blog Starter](/examples/blog-starter)
|
- [Blog Starter](/examples/blog-starter)
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|