├── .gitignore
├── .gitpod.yml
├── .vscode
└── settings.json
├── README.md
├── app
├── layout.tsx
├── page.tsx
└── posts
│ └── [slug]
│ └── page.tsx
├── components
├── Button.tsx
└── Header.tsx
├── contentlayer.config.ts
├── next-env.d.ts
├── next.config.js
├── package.json
├── postcss.config.js
├── posts
├── change-me.mdx
├── click-me.mdx
└── what-is-contentlayer.mdx
├── public
└── images
│ └── favicon.png
├── styles
└── globals.css
├── tailwind.config.js
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # next
5 | dist
6 | .next
7 |
8 | # contentlayer
9 | .contentlayer
10 |
11 | # yarn
12 | yarn.lock
13 | yarn-error.log
14 |
15 | # mac
16 | .DS_Store
17 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - init: yarn install && yarn contentlayer build
3 | command: gp open posts/change-me.md && yarn dev
4 |
5 | ports:
6 | - port: 3000
7 | onOpen: open-preview
8 |
9 | github:
10 | prebuilds:
11 | addCheck: true
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "typescript.enablePromptUseWorkspaceTsdk": true
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js Contentlayer Example
2 |
3 | ## Demo
4 |
5 | View the deployed project: [Demo](https://next-contentlayer-example.vercel.app/)
6 |
7 | ## Try it Now
8 |
9 | [](http://gitpod.io/#https://github.com/contentlayerdev/next-contentlayer-example)
10 |
11 | ## Local Installation
12 |
13 | Clone the project:
14 |
15 | git clone git@github.com:contentlayerdev/next-contentlayer-example.git
16 |
17 | Install dependencies:
18 |
19 | yarn
20 |
21 | Run dev server:
22 |
23 | yarn dev
24 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 |
3 | import { Header } from '../components/Header'
4 |
5 | export default function RootLayout({ children }: { children: React.ReactNode }) {
6 | return (
7 |
8 |
9 | Contentlayer Next.js Example
10 |
11 |
12 |
13 |
14 | {children}
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { compareDesc, format, parseISO } from "date-fns";
3 | import { allPosts, Post } from "contentlayer/generated";
4 | import { getMDXComponent } from "next-contentlayer/hooks";
5 |
6 | function PostCard(post: Post) {
7 | const Content = getMDXComponent(post.body.code);
8 |
9 | return (
10 |
11 |
12 |
16 | {post.title}
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
29 | export default function Home() {
30 | const posts = allPosts.sort((a, b) =>
31 | compareDesc(new Date(a.date), new Date(b.date))
32 | );
33 |
34 | return (
35 |
36 |
Next.js Example
37 |
38 | {posts.map((post, idx) => (
39 |
40 | ))}
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/app/posts/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import { format, parseISO } from 'date-fns'
2 | import { allPosts } from 'contentlayer/generated'
3 | import { getMDXComponent } from 'next-contentlayer/hooks'
4 |
5 | export const generateStaticParams = async () => allPosts.map((post) => ({ slug: post._raw.flattenedPath }))
6 |
7 | export const generateMetadata = ({ params }) => {
8 | const post = allPosts.find((post) => post._raw.flattenedPath === params.slug)
9 | return { title: post.title }
10 | }
11 |
12 | const PostLayout = ({ params }: { params: { slug: string } }) => {
13 | const post = allPosts.find((post) => post._raw.flattenedPath === params.slug)
14 |
15 | const Content = getMDXComponent(post.body.code)
16 |
17 | return (
18 |
19 |
20 |
23 |
{post.title}
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default PostLayout
31 |
--------------------------------------------------------------------------------
/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import React from 'react'
3 |
4 | export const Button: FC<{ title: string }> = ({ title }) => (
5 | alert('Hi')}
8 | >
9 | {title}
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | function Icon() {
4 | return (
5 |
15 | )
16 | }
17 |
18 | function Logo() {
19 | return (
20 |
21 |
22 |
23 |
24 | Contentlayer
25 |
26 | )
27 | }
28 |
29 | export function Header() {
30 | return (
31 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/contentlayer.config.ts:
--------------------------------------------------------------------------------
1 | import { defineDocumentType, makeSource } from 'contentlayer/source-files'
2 |
3 | const Post = defineDocumentType(() => ({
4 | name: 'Post',
5 | filePathPattern: `**/*.mdx`,
6 | contentType: 'mdx',
7 | fields: {
8 | title: {
9 | type: 'string',
10 | description: 'The title of the post',
11 | required: true,
12 | },
13 | date: {
14 | type: 'date',
15 | description: 'The date of the post',
16 | required: true,
17 | },
18 | },
19 | computedFields: {
20 | url: {
21 | type: 'string',
22 | resolve: (doc) => `/posts/${doc._raw.flattenedPath}`,
23 | },
24 | },
25 | }))
26 |
27 | export default makeSource({
28 | contentDirPath: 'posts',
29 | documentTypes: [Post],
30 | })
31 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const { withContentlayer } = require("next-contentlayer");
2 |
3 | /** @type {import('next').NextConfig} */
4 | const nextConfig = {
5 | experimental: {
6 | appDir: true,
7 | },
8 | };
9 |
10 | module.exports = withContentlayer(nextConfig);
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-contentlayer-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "contentlayer": "latest",
12 | "date-fns": "2.30.0",
13 | "next": "13.4.7",
14 | "next-contentlayer": "latest",
15 | "react": "18.2.0",
16 | "react-dom": "18.2.0"
17 | },
18 | "devDependencies": {
19 | "@types/react": "18.2.14",
20 | "autoprefixer": "^10.4.14",
21 | "postcss": "^8.4.24",
22 | "tailwindcss": "^3.3.2",
23 | "typescript": "5.1.6"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/posts/change-me.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Change me!
3 | date: 2022-03-11
4 | ---
5 |
6 | When you change a source file, Contentlayer automatically updates the content cache, which prompts Next.js to reload the content on screen.
7 |
--------------------------------------------------------------------------------
/posts/click-me.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Click me!
3 | date: 2022-02-28
4 | ---
5 |
6 | Blog posts have their own pages. The content source is a markdown file, parsed to HTML by Contentlayer.
7 |
--------------------------------------------------------------------------------
/posts/what-is-contentlayer.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: What is Contentlayer?
3 | date: 2022-02-22
4 | ---
5 |
6 | **Contentlayer makes working with content easy.** It is a content preprocessor that validates and transforms your content into type-safe JSON you can easily import into your application.
7 |
--------------------------------------------------------------------------------
/public/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/next-contentlayer-example/88da08590d6a19c9d1678007c0f2a40513d12981/public/images/favicon.png
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;700&display=swap");
2 |
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
7 | p {
8 | @apply mb-4;
9 | }
10 |
11 | h1,
12 | h2,
13 | h3,
14 | h4,
15 | h5,
16 | h6 {
17 | @apply font-bold
18 | mb-1;
19 | }
20 |
21 | h1 {
22 | @apply text-3xl;
23 | }
24 |
25 | h2 {
26 | @apply text-xl;
27 | }
28 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const defaultTheme = require("tailwindcss/defaultTheme");
2 |
3 | module.exports = {
4 | content: [
5 | "./app/**/*.{js,ts,jsx,tsx}",
6 | "./pages/**/*.{js,ts,jsx,tsx}",
7 | "./components/**/*.{js,ts,jsx,tsx}",
8 | ],
9 | theme: {
10 | extend: {
11 | fontFamily: {
12 | sans: ["Inter", ...defaultTheme.fontFamily.sans],
13 | },
14 | },
15 | },
16 | plugins: [],
17 | };
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": false,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "incremental": true,
11 | "esModuleInterop": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve",
17 | "baseUrl": ".",
18 | "paths": {
19 | "contentlayer/generated": ["./.contentlayer/generated"]
20 | },
21 | "plugins": [{ "name": "next" }]
22 | },
23 | "include": [
24 | "next-env.d.ts",
25 | "**/*.ts",
26 | "**/*.tsx",
27 | ".contentlayer/generated",
28 | ".next/types/**/*.ts"
29 | ],
30 | "exclude": ["node_modules"]
31 | }
32 |
--------------------------------------------------------------------------------