├── .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 | [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-908a85?logo=gitpod)](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 | 6 | 14 | 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 |
32 | 33 |
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 | --------------------------------------------------------------------------------