├── .eslintrc.json
├── .gitignore
├── .gitpod.yml
├── .nvmrc
├── .prettierrc
├── .vscode
└── settings.json
├── .yarn
├── patches
│ └── @stackblitz-sdk-npm-1.7.0-alpha.3-1f8676ad43.patch
├── plugins
│ └── @yarnpkg
│ │ ├── plugin-interactive-tools.cjs
│ │ └── plugin-typescript.cjs
└── releases
│ └── yarn-3.2.0.cjs
├── .yarnrc.yml
├── LICENSE
├── README.md
├── assets
└── contentlayer-generated
│ ├── index.d.ts
│ └── types.d.ts
├── content
├── blog
│ ├── beta.mdx
│ └── working-with-content-is-hard-for-developers.mdx
├── config
│ └── global.yaml
├── docs
│ ├── 100-getting-started
│ │ └── index.mdx
│ ├── 200-concepts
│ │ ├── 100-how-contentlayer-works.mdx
│ │ ├── 200-content-modeling.mdx
│ │ ├── 300-type-safety.mdx
│ │ ├── 400-comparison.mdx
│ │ └── index.mdx
│ ├── 300-sources
│ │ ├── 100-files
│ │ │ ├── 100-mapping-document-types.mdx
│ │ │ ├── 200-generated-data.mdx
│ │ │ ├── 300-generated-types.mdx
│ │ │ ├── 400-mdx.mdx
│ │ │ ├── 500-images.mdx
│ │ │ └── index.mdx
│ │ ├── 125-notion
│ │ │ ├── 100-getting-started.mdx
│ │ │ ├── 200-configure-databases.mdx
│ │ │ ├── 300-configure-properties.mdx
│ │ │ ├── 400-configure-renderer.mdx
│ │ │ ├── 500-images.mdx
│ │ │ └── index.mdx
│ │ ├── 150-remote-files.mdx
│ │ ├── 200-contentful.mdx
│ │ ├── 300-sanity.mdx
│ │ └── index.mdx
│ ├── 400-environments
│ │ ├── 100-nextjs.mdx
│ │ ├── 200-remix.mdx
│ │ ├── 300-svelte.mdx
│ │ ├── 400-astro.mdx
│ │ ├── 500-vite.mdx
│ │ └── index.mdx
│ ├── 500-reference
│ │ ├── 100-cli.mdx
│ │ ├── 200-next-contentlayer.mdx
│ │ ├── 200-source-files
│ │ │ ├── 100-make-source.mdx
│ │ │ ├── 200-define-document-type.mdx
│ │ │ ├── 300-define-nested-type.mdx
│ │ │ ├── 400-field-types.mdx
│ │ │ └── index.mdx
│ │ ├── 250-source-notion
│ │ │ ├── 100-make-source.mdx
│ │ │ ├── 200-define-database.mdx
│ │ │ ├── 300-properties.mdx
│ │ │ └── index.mdx
│ │ └── index.mdx
│ ├── 600-integrations
│ │ ├── 100-stackbit
│ │ │ ├── 100-tutorial.mdx
│ │ │ ├── 200-config.mdx
│ │ │ ├── 300-dev-server.mdx
│ │ │ └── index.mdx
│ │ └── index.mdx
│ ├── 700-other
│ │ ├── 100-faq.mdx
│ │ ├── 200-roadmap.mdx
│ │ ├── 300-changelog.mdx
│ │ ├── 400-contributing.mdx
│ │ ├── 500-known-problems.mdx
│ │ └── index.mdx
│ └── index.mdx
└── examples
│ ├── 100-nextjs.mdx
│ ├── 200-vite.mdx
│ ├── 900-other.mdx
│ └── index.mdx
├── contentlayer.config.ts
├── netlify.toml
├── next-env.d.ts
├── next-sitemap.js
├── next.config.js
├── package.json
├── postcss.config.js
├── public
├── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-167x167.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── favicon-128x128.png
│ ├── favicon-16x16.png
│ ├── favicon-196x196.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ └── mstile-70x70.png
├── fonts
│ └── virgil.woff2
└── images
│ ├── beta-launch-post-meta.png
│ ├── content-as-data.png
│ ├── content-is-hard-meta.png
│ ├── content-timeline.png
│ ├── contentlayer-in-five-minutes.png
│ ├── intro-thumbnail.jpg
│ ├── local-data-transformation.png
│ ├── logos
│ ├── astro.svg
│ ├── contentful.svg
│ ├── mdx.svg
│ ├── nextjs.svg
│ ├── notion.svg
│ ├── remix.svg
│ ├── sanity.svg
│ └── vite.svg
│ ├── notion-contentlayer-source.png
│ ├── notion
│ ├── database.png
│ └── integration_granular_permissions.gif
│ ├── performance-comparison.png
│ ├── playground-hint-dev-server.png
│ ├── playground-hint-edit.png
│ ├── playground-hint-updates.png
│ ├── post-feed.png
│ ├── post-layout.png
│ └── stackbit-visual-editing.gif
├── scripts
└── generate-page-ids.mjs
├── src
├── components
│ ├── ColorSchemeContext.tsx
│ ├── SearchContext.tsx
│ ├── blog
│ │ ├── BenchmarkResults.tsx
│ │ ├── BlogDetails.tsx
│ │ ├── BlogHeader.tsx
│ │ ├── BlogPreview.tsx
│ │ ├── BulletList.tsx
│ │ ├── ContentStack.tsx
│ │ ├── Playground.tsx
│ │ ├── RelatedPosts.tsx
│ │ └── TLDR.tsx
│ ├── common
│ │ ├── Arrow
│ │ │ ├── CurvedLong.tsx
│ │ │ ├── CurvedShort.tsx
│ │ │ ├── LoopedLong.tsx
│ │ │ ├── LoopedShort.tsx
│ │ │ ├── StraightDashed.tsx
│ │ │ ├── StraightLong.tsx
│ │ │ ├── StraightShort.tsx
│ │ │ └── index.tsx
│ │ ├── Author.tsx
│ │ ├── Button.tsx
│ │ ├── Callout.tsx
│ │ ├── Card.tsx
│ │ ├── ChevronLink.tsx
│ │ ├── ColorSchemeSwitcher.tsx
│ │ ├── Container.tsx
│ │ ├── Footer.tsx
│ │ ├── Headings.tsx
│ │ ├── Icon
│ │ │ ├── API.tsx
│ │ │ ├── Bars.tsx
│ │ │ ├── BrokenLink.tsx
│ │ │ ├── Calendar.tsx
│ │ │ ├── Check.tsx
│ │ │ ├── CheckCircle.tsx
│ │ │ ├── CheckCircleOutline.tsx
│ │ │ ├── ChevronDown.tsx
│ │ │ ├── ChevronLeft.tsx
│ │ │ ├── ChevronRight.tsx
│ │ │ ├── Close.tsx
│ │ │ ├── Code.tsx
│ │ │ ├── CodeLight.tsx
│ │ │ ├── Collapse.tsx
│ │ │ ├── Contentful.tsx
│ │ │ ├── Contentlayer.tsx
│ │ │ ├── CrossCircleOutline.tsx
│ │ │ ├── Database.tsx
│ │ │ ├── Discord.tsx
│ │ │ ├── Exclamation.tsx
│ │ │ ├── Expand.tsx
│ │ │ ├── ExternalLink.tsx
│ │ │ ├── Gear.tsx
│ │ │ ├── GitHub.tsx
│ │ │ ├── Gitpod.tsx
│ │ │ ├── GraphQL.tsx
│ │ │ ├── Info.tsx
│ │ │ ├── Jekyll.tsx
│ │ │ ├── Lightning.tsx
│ │ │ ├── Markdown.tsx
│ │ │ ├── Moon.tsx
│ │ │ ├── Notion.tsx
│ │ │ ├── PHP.tsx
│ │ │ ├── PlayButton.tsx
│ │ │ ├── Plus.tsx
│ │ │ ├── Question.tsx
│ │ │ ├── React.tsx
│ │ │ ├── Rocket.tsx
│ │ │ ├── Search.tsx
│ │ │ ├── Sign.tsx
│ │ │ ├── Sun.tsx
│ │ │ ├── Template.tsx
│ │ │ ├── Users.tsx
│ │ │ ├── WordPress.tsx
│ │ │ └── index.tsx
│ │ ├── Label.tsx
│ │ ├── Link.tsx
│ │ ├── Logo.tsx
│ │ ├── MainNavigation.tsx
│ │ ├── PageNavigation.tsx
│ │ └── User.tsx
│ ├── docs
│ │ ├── DocsCard.tsx
│ │ ├── DocsFooter.tsx
│ │ ├── DocsHeader.tsx
│ │ ├── DocsNavigation.tsx
│ │ └── OptionsTable.tsx
│ ├── examples
│ │ └── ExamplesFooter.tsx
│ └── landing-page
│ │ ├── Checklist.tsx
│ │ ├── CodeWindow.tsx
│ │ ├── Dashed.tsx
│ │ ├── DataTransformation.tsx
│ │ ├── FAQ.tsx
│ │ ├── Features.tsx
│ │ ├── FileTree.tsx
│ │ ├── Heading.tsx
│ │ ├── Hero.tsx
│ │ ├── HowItWorks.tsx
│ │ ├── Paragraph.tsx
│ │ ├── Playground.tsx
│ │ ├── Support.tsx
│ │ ├── Testimonials.tsx
│ │ ├── Tweets.tsx
│ │ └── Video.tsx
├── contentlayer
│ ├── document
│ │ ├── Doc.ts
│ │ ├── Example.ts
│ │ ├── GlobalConfig.ts
│ │ └── Post.ts
│ ├── index.ts
│ ├── nested
│ │ ├── Link.ts
│ │ └── SEO.ts
│ └── utils.ts
├── pages
│ ├── 404.tsx
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── blog
│ │ ├── [slug].tsx
│ │ └── index.tsx
│ ├── docs
│ │ └── [[...slug]].tsx
│ ├── examples
│ │ └── [[...slug]].tsx
│ └── index.tsx
├── styles
│ ├── globals.css
│ ├── hljs-github-dark.css
│ ├── markdown.css
│ ├── tailwind.css
│ └── twoslash-shiki.css
└── utils
│ ├── blog
│ └── beta-post-snippets.ts
│ ├── build-docs-tree.ts
│ ├── build-examples-tree.ts
│ ├── helpers.ts
│ ├── next.ts
│ ├── object.ts
│ ├── sluggify.ts
│ ├── syntax-highlighting.ts
│ ├── used-by-count.ts
│ └── validate-duplicate-ids.ts
├── tailwind.config.js
├── tsconfig.json
├── types
├── PathSegment.d.ts
└── TreeNode.d.ts
├── vercel.json
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["plugin:import/recommended", "next/core-web-vitals"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.yarn/*
2 | !/.yarn/releases
3 | !/.yarn/plugins
4 | !/.yarn/sdks
5 | !/.yarn/patches
6 |
7 | # dependencies
8 | /node_modules
9 | /.pnp
10 | .pnp.js
11 |
12 | # testing
13 | /coverage
14 |
15 | # next.js
16 | /.next/
17 | /out/
18 |
19 | # production
20 | /build
21 |
22 | # misc
23 | .DS_Store
24 | *.pem
25 | tmp
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 |
32 | # local env files
33 | .env.local
34 | .env.development.local
35 | .env.test.local
36 | .env.production.local
37 |
38 | # vercel
39 | .vercel
40 |
41 | # contentlayer
42 | .contentlayer
43 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - init: npm install
3 | command: npm run dev
4 | ports:
5 | - port: 3000
6 | onOpen: open-preview
7 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "semi": false,
4 | "trailingComma": "all",
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "editor.codeActionsOnSave": {
4 | "source.organizeImports": "explicit",
5 | "source.fixAll": "explicit",
6 | "source.addMissingImports": "explicit"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | nodeLinker: node-modules
2 |
3 | plugins:
4 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
5 | spec: '@yarnpkg/plugin-typescript'
6 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
7 | spec: '@yarnpkg/plugin-interactive-tools'
8 |
9 | yarnPath: .yarn/releases/yarn-3.2.0.cjs
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Stackbit Inc., Johannes Schickling
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Contentlayer Website
2 |
3 | [](https://gitpod.io/#https://github.com/contentlayerdev/website)
4 |
5 | ## Local setup
6 |
7 | ```bash
8 | npm install
9 | npm run dev
10 | ```
11 |
12 | Open [https://localhost:3000](https://localhost:3000) with your browser to see the result. (Note the webserver is using a self-signed SSL certificate since HTTPS is required for the embedded Stackblitz editor to work properly.)
13 |
14 | ## Live preview
15 |
16 | [Current Vercel Deployment](https://website-git-new-landing-page-schick.vercel.app)
17 |
18 | ## Generating Global Doc ID
19 |
20 | Every document (except docs index page) has a unique eight-character `global_id` property. This uniquely identifies that piece of documentation and provides a seamless way to be able to reorganize documentation without worrying about 404 errors for missing redirects.
21 |
22 | These can be automatically generated by running the following command:
23 |
24 | node scripts/generate-page-ids.mjs
25 |
26 | Validate that all `global_id` values are unique with this command:
27 |
28 | node scripts/validate-duplicate-ids.mjs
29 |
--------------------------------------------------------------------------------
/assets/contentlayer-generated/index.d.ts:
--------------------------------------------------------------------------------
1 | // NOTE This file is auto-generated by the Contentlayer CLI
2 |
3 | import { Post, DocumentTypes } from './types'
4 |
5 | export type * from './types'
6 |
7 | export declare const allPosts: Post[]
8 |
9 | export declare const allDocuments: DocumentTypes[]
10 |
--------------------------------------------------------------------------------
/assets/contentlayer-generated/types.d.ts:
--------------------------------------------------------------------------------
1 | // NOTE This file is auto-generated by the Contentlayer CLI
2 |
3 | import type { Markdown, MDX } from 'contentlayer/core'
4 | import * as Local from 'contentlayer/source-files'
5 |
6 | export { isType } from 'contentlayer/client'
7 |
8 | // export type Image = string
9 | export type { Markdown, MDX }
10 |
11 | /** Document types */
12 |
13 | export type Post = {
14 | /** File path relative to `contentDirPath` */
15 | _id: string
16 | _raw: Local.RawDocumentData
17 | type: 'Post'
18 | /** The title of the page */
19 | title: string
20 | /** Markdown file body */
21 | body: Markdown
22 | /** The URL path of this page relative to site root. For example, the site root page would be "/", and doc page would be "docs/getting-started/" */
23 | url: string
24 | }
25 |
26 | /** Nested types */
27 |
28 | /** Helper types */
29 |
30 | export type AllTypes = DocumentTypes | NestedTypes
31 | export type AllTypeNames = DocumentTypeNames | NestedTypeNames
32 |
33 | export type DocumentTypes = Post
34 | export type DocumentTypeNames = 'Post'
35 |
36 | export type NestedTypes = never
37 | export type NestedTypeNames = never
38 |
39 | export interface ContentlayerGenTypes {
40 | documentTypes: DocumentTypes
41 | documentTypeMap: DocumentTypeMap
42 | documentTypeNames: DocumentTypeNames
43 | nestedTypes: NestedTypes
44 | nestedTypeMap: NestedTypeMap
45 | nestedTypeNames: NestedTypeNames
46 | allTypeNames: AllTypeNames
47 | }
48 |
49 | // declare global {
50 | // interface ContentlayerGen extends ContentlayerGenTypes {}
51 | // }
52 |
53 | export type DocumentTypeMap = {
54 | Post: Post
55 | }
56 |
57 | export type NestedTypeMap = {}
58 |
--------------------------------------------------------------------------------
/content/config/global.yaml:
--------------------------------------------------------------------------------
1 | title: Contentlayer
2 |
--------------------------------------------------------------------------------
/content/docs/200-concepts/200-content-modeling.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: dc68721f
3 | title: Content Modeling with Contentlayer
4 | nav_title: Content Modeling
5 | excerpt: Why content modeling is necessary and how it differs between local and remote sources.
6 | ---
7 |
8 | Contentlayer's primary job is to transform your content into data that can be imported into the pages of your web project. To do that effectively, Contentlayer has to know the shape of your content — your _content schema_.
9 |
10 | This is handled differently depending on whether you're working with a local source (Files) or a remote source.
11 |
12 | ## Remote Sources
13 |
14 | Remote sources are experimental. Expect this section to change.
15 |
16 | With remote sources — i.e. headless CMS — the content schema is pulled in automatically from the source's API.
17 |
18 | However, we're still experimenting with exactly how remote sources will work. As we continue to explore and add new sources, we'll update this section.
19 |
20 | ## Local Sources
21 |
22 | When using a local source, your schema must be explicitly defined so Contentlayer knows how to handle the local files.
23 |
24 | You can learn more about local sources by [exploring the files source guides](/docs/sources/files).
25 |
26 | Below you'll find a few concepts that are important to understand when modeling content locally.
27 |
28 | ### Documents vs Nested
29 |
30 | There are two types of objects — _documents_ and _nested (documents)_.
31 |
32 | **Documents** are objects generated by Contentlayer, according to a _document type definition_. Think of a document type like a model or a table in a database. A document is then an instance of that model — i.e. the result of a database query.
33 |
34 | Contentlayer writes documents to individual JSON files, which can then be imported into your project's pages.
35 |
36 | **Nested** documents are objects that are embedded directly in a document. They are defined as _nested types_ and are primarily used for repeatable shapes.
37 |
38 | For example, suppose you had an SEO object that you wanted included on various models. You could define the nested document type once, and then use it within any model that requires it. The [`defineNestedType` API reference](/docs/reference/source-files/define-nested-type) has more information and examples.
39 |
40 | ### Content References
41 |
42 | Just like in a database, content can be associated with other content. For example, a post document type can reference tag documents.
43 |
44 | However, [references are not currently transformed or embedded](/docs/reference/source-files/field-types#reference). They are passed through as a file path reference to the related object. When working with references, we recommend building utility functions that will retrieve associated data as needed.
45 |
--------------------------------------------------------------------------------
/content/docs/200-concepts/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: ac167d19
3 | title: Concepts
4 | show_child_cards: true
5 | excerpt: Background information on how Contentlayer works.
6 | ---
7 |
8 | This is the starting point for learning what Contentlayer is all about. Why we built Contentlayer, how it works, comparison to other products, and the practices we believe in.
9 |
--------------------------------------------------------------------------------
/content/docs/300-sources/100-files/300-generated-types.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: fda1d801
3 | title: Generated Type Definitions
4 | excerpt: Contentlayer automatically generates type definitions for content that lives locally.
5 | ---
6 |
7 | Alongside your [generated documents](/docs/sources/files/generated-data) you'll find a `types` file at `.contentlayer/generated/index.d.ts`.
8 |
9 | This file exports a number of auto-generated and provided types that you can use to ensure type safety throughout your application.
10 |
11 | ## Generated Types
12 |
13 | A type is generated for every document and nested type you've defined in your Contentlayer configuration.
14 |
15 | These types will match the structure of your documents, including both generated and reserved properties.
16 |
17 | Consider an example where you have a `Page` document type that has three fields: `title`, `description`, and `seo`, along with `body` content. And let's say the `seo` field is a nested object of type `SEO`, also with a `title` and `description`.
18 |
19 | The exported types will look something like this:
20 |
21 | ```ts
22 | export type SEO = {
23 | type: 'SEO'
24 | title: string
25 | description: string | undefined
26 | }
27 |
28 | export type Page = {
29 | _id: string
30 | _raw: Local.RawDocumentData
31 | type: 'Page'
32 | title: string
33 | description: string | undefined
34 | seo: SEO | undefined
35 | body: Markdown
36 | }
37 | ```
38 |
39 | ## Provided Types
40 |
41 | This file also exports a number of provided types that may come in handy in your project. Though there are more than you could inspect, here are those that may be of most use to you:
42 |
43 | - `Markdown`: Object with properties `raw` and `html`.
44 | - `MDX`: Object with properties `raw` and `code`.
45 | - `AllTypes`: Accepts any document type or nested type.
46 | - `DocumentTypes`: Accepts any document type.
47 | - `NestedTypes`: Accepts any nested type.
48 |
--------------------------------------------------------------------------------
/content/docs/300-sources/100-files/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: ae74398f
3 | title: Files Source
4 | nav_title: Files
5 | collapsible: true
6 | collapsed: true
7 | excerpt: Guides for working with Contentlayer when using local files as your content source.
8 | ---
9 |
10 | Using a files source with Contentlayer means that content lives locally, right alongside your code in your web project repository. These is most often markdown or MDX files, but can also be content in the JSON or YAML syntax.
11 |
12 |
13 |
19 |
20 |
Our getting started tutorial is the best place to start when wanting to work with local content.
21 |
It shows you how to work with local markdown files within a Next.js application.
22 |
23 |
24 |
25 | ---
26 |
27 | ## Local Source Guides
28 |
29 |
30 |
Local source guides will teach you the crucial aspects of working with local content.
31 |
32 | -
33 | -
34 | -
35 | -
36 | -
37 |
38 |
39 |
40 | ---
41 |
42 | ## API Reference
43 |
44 |
45 |
46 | Working with local content uses the `contentlayer/local-source` module.
47 |
48 |
49 |
50 | -
51 | -
52 | -
53 | -
54 |
55 |
56 |
--------------------------------------------------------------------------------
/content/docs/300-sources/125-notion/500-images.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: e3a04016
3 | title: Images
4 | nav_title: Images
5 | excerpt: Options on image processing when using Notion as your content source.
6 | ---
7 |
8 |
9 | ⚠ The content source plugin `contentlayer-source-notion` is currently in Alpha and should not be used in production.
10 |
11 |
12 | ---
13 |
14 | When querying images from Notion it give a temporary link that expires after a certain amount of time.
15 |
16 | Contentlayer does not currently support image processing, though we're [planning on implementing it](https://github.com/contentlayerdev/contentlayer/issues/11).
17 | Until then, there are a few different approaches you can take to working with images when your content is processed by Contentlayer:
18 |
19 | ## Using an Image Management Services
20 |
21 | Alternatively, you can use an image management service like [Cloudinary](https://cloudinary.com/) or [Imgix](https://imgix.com/). (There are many to choose from.)
22 |
23 | These services allow you to use URL parameters to determine the size and shape of your image. The image is then optimized by these services and delivered to your users.
24 |
25 | These services deliver an optimized image, but it's still up to you to ensure that image doesn't degrade the performance of your pages. Therefore, you may also want to consider manually processing the images. See below for details.
26 |
27 | ### Processing Images as Computed Fields
28 |
29 | To process images during the Contentlayer build, you can use a computed field.
30 | Say you've a property `thumbnail`.
31 |
32 | Then you'd add a _computed_ field like `image` that would do the processing and returns the path/url to the processed image.
33 |
34 | ```js
35 | defineDatabase(() => ({
36 | name: 'Post',
37 | computedFields: {
38 | thumbnail: {
39 | type: 'string',
40 | resolve: (doc) => {
41 | // do the processing, and return new value ...
42 | },
43 | },
44 | },
45 | }))
46 | ```
47 |
48 | ### Processing after the Contentlayer Build
49 |
50 | Another option is to build a utility method within your application that does the processing. In this case, Contentlayer would have already processed the image reference as a string, which you would send to the processing utility before building the props to send to the page.
51 |
52 | ### Handling Images in Markdown with Contentlayer
53 |
54 | It's worth noting that the methods above handle processing images only that were specified in the frontmatter. If you want to process images you've used within the body of a markdown or MDX file, you'll want to build (or find) a remark or rehype plugin.
55 |
--------------------------------------------------------------------------------
/content/docs/300-sources/125-notion/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: b2ce5957
3 | title: Notion Source
4 | nav_title: Notion
5 | label: Experimental
6 | collapsible: true
7 | collapsed: true
8 | excerpt: Guides for working with Contentlayer when using Notion as your content source.
9 | ---
10 |
11 |
12 | ⚠ The content source plugin `contentlayer-source-notion` is currently in Alpha and should not be used in production.
13 |
14 |
15 | This plugin allows you to use Notion as a content source. It automatically generates type by inferring your page properties and transform Rich Text into HTML.
16 |
17 |
18 |
23 | Our getting started tutorial shows you how to use Notion to manage your Next.JS Blog.
24 |
25 |
26 |
Follow the getting started tutorial to start working with Notion as a Content source.
27 |
28 |
29 |
30 | ---
31 |
32 | ## Notion Source Guides
33 |
34 |
35 |
Notion source guides will teach you the crucial aspects of working with Notion.
36 |
37 | -
38 | -
39 | -
40 | -
41 | -
42 |
43 |
44 |
45 | ---
46 |
47 | ## API Reference
48 |
49 |
50 |
51 | Working with Notion uses the `contentlayer/notion-source` module.
52 |
53 |
54 |
55 | -
56 | -
57 | -
58 |
59 |
60 |
--------------------------------------------------------------------------------
/content/docs/300-sources/150-remote-files.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: fbb47906
3 | title: Remote Files
4 | label: Experimental
5 | excerpt: How to use Contentlayer with remote files as content source (e.g. Git repo)
6 | ---
7 |
8 | The remote files source acts very similarly to the [files source](/docs/sources/files) with the difference that the content files can live outside the website folder. Using the remote files source Contentlayer will automatically **sync the content files from your remote files source to your local website folder** and then process them using the regular files source.
9 |
10 | Common remote file sources including other Git repositories, databases, APIs or anywhere else where your content comes from.
11 |
12 | ## Example: Remote Git Repository
13 |
14 | See [full example here](https://github.com/contentlayerdev/contentlayer/tree/main/examples/node-script-remote-content).
15 |
16 | ```ts
17 | // NOTE we're using the `defineDocumentType` from the regular files source
18 | import { defineDocumentType } from '@contentlayer/source-files'
19 | import { spawn } from 'node:child_process'
20 | import { makeSource } from '@contentlayer/source-remote-files'
21 |
22 | const Post = defineDocumentType(() => ({
23 | name: 'Post',
24 | filePathPattern: `docs/**/*.md`,
25 | fields: {
26 | title: { type: 'string', required: false },
27 | },
28 | }))
29 |
30 | const syncContentFromGit = async (contentDir: string) => {
31 | const syncRun = async () => {
32 | const gitUrl = 'https://github.com/vercel/next.js.git'
33 | // TODO: git pull or git clone (see full example for working code)
34 | }
35 |
36 | let wasCancelled = false
37 | let syncInterval
38 |
39 | const syncLoop = async () => {
40 | await syncRun()
41 |
42 | if (wasCancelled) return
43 |
44 | syncInterval = setTimeout(syncLoop, 1000 * 60)
45 | }
46 |
47 | // Block until the first sync is done
48 | await syncLoop()
49 |
50 | return () => {
51 | wasCancelled = true
52 | clearTimeout(syncInterval)
53 | }
54 | }
55 |
56 | export default makeSource({
57 | syncFiles: syncContentFromGit,
58 | contentDirPath: 'nextjs-repo',
59 | contentDirInclude: ['docs'],
60 | documentTypes: [Post],
61 | disableImportAliasWarning: true,
62 | })
63 | ```
64 |
65 | ## Notes
66 |
67 | - It's recommended to add the synced content directory to your `.gitignore` file
68 | - It's possible to combine the regular files source and the remote files source. You'll have to use the same root `contentDirPath` and put the respective content in different sub directories.
69 |
--------------------------------------------------------------------------------
/content/docs/300-sources/200-contentful.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: d72dff47
3 | title: Contentful
4 | label: Planned
5 | excerpt: We are planning to officially support Contentful. Add your vote to help us prioritize the work.
6 | ---
7 |
8 | Support for Contentful is planned, but it is not yet stable. To help us prioritize this work, please add your vote to [this GitHub issue](https://github.com/contentlayerdev/contentlayer/issues/173).
9 |
--------------------------------------------------------------------------------
/content/docs/300-sources/300-sanity.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: c4fdd0f7
3 | title: Sanity
4 | label: Considering
5 | excerpt: We're considering officially supporting Sanity. Add your vote.
6 | ---
7 |
8 | We're currently considering support for Sanity. Please add your vote to [this GitHub issue](https://github.com/contentlayerdev/contentlayer/issues/172) to help us prioritize the work.
9 |
--------------------------------------------------------------------------------
/content/docs/300-sources/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: acc47cf6
3 | title: Content Sources
4 | nav_title: Sources
5 | excerpt: Solving specific scenarios using Contentlayer.
6 | show_child_cards: true
7 | ---
8 |
9 | Contentlayer lets you choose the source for your content.
10 |
11 | Currently only local file source is officially supported. Visit the others for instructions on how to add your vote.
12 |
13 | [Join our Discord community](https://discord.gg/rytFErsARm) to suggest new sources.
14 |
--------------------------------------------------------------------------------
/content/docs/400-environments/200-remix.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: b3975f99
3 | title: Remix
4 | label: Considering
5 | excerpt: We're considering officially supporting Remix. Add your vote.
6 | ---
7 |
8 | We're currently considering support for Remix. Please add your vote to [this GitHub issue](https://github.com/contentlayerdev/contentlayer/issues/169) to help us prioritize the work.
9 |
--------------------------------------------------------------------------------
/content/docs/400-environments/300-svelte.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: efe6735d
3 | title: SvelteKit
4 | label: Considering
5 | excerpt: We're considering officially supporting Svelte and SvelteKit. Add your vote.
6 | ---
7 |
8 | We're currently considering support for SvelteKit. Please add your vote to [this GitHub issue](https://github.com/contentlayerdev/contentlayer/issues/170) to help us prioritize the work.
9 |
--------------------------------------------------------------------------------
/content/docs/400-environments/400-astro.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: d57135c9
3 | title: Astro
4 | label: Considering
5 | excerpt: We're considering officially supporting Astro. Add your vote.
6 | ---
7 |
8 | We're currently considering support for Astro. Please add your vote to [this GitHub issue](https://github.com/contentlayerdev/contentlayer/issues/171) to help us prioritize the work.
9 |
--------------------------------------------------------------------------------
/content/docs/400-environments/500-vite.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: ba80b355
3 | title: Vite
4 | label: Considering
5 | excerpt: We're considering officially supporting Vite. Add your vote.
6 | ---
7 |
8 | We're currently considering support for Vite. Please add your vote to [this GitHub issue](https://github.com/contentlayerdev/contentlayer/issues/179) to help us prioritize the work.
9 |
--------------------------------------------------------------------------------
/content/docs/400-environments/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: c6df80ab
3 | title: Environments
4 | excerpt: Solving specific scenarios using Contentlayer.
5 | show_child_cards: true
6 | ---
7 |
8 | Contentlayer supports a tight integration with Next.js.
9 |
10 | Other environment support is coming soon. Visit those we're considering below for instructions on how to add your vote.
11 |
12 | [Join our Discord community](https://discord.gg/rytFErsARm) to suggest new environments.
13 |
--------------------------------------------------------------------------------
/content/docs/500-reference/100-cli.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: e9e2f788
3 | title: Contentlayer CLI
4 | nav_title: CLI
5 | excerpt: Working with Contentlayer on the command line.
6 | ---
7 |
8 | The Contentlayer CLI enables you to transform your content into data.
9 |
10 | ## Usage
11 |
12 | ```txt
13 | contentlayer [options]
14 | ```
15 |
16 | To get a list of all commands, you can run `contentlayer` with the `--help` flag:
17 |
18 | ```txt
19 | contentlayer --help
20 | ```
21 |
22 | Which will produce an output that looks like this:
23 |
24 | ```txt
25 | ━━━ Contentlayer CLI - 0.0.34 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
26 |
27 | $ contentlayer
28 |
29 | ━━━ General commands ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
30 |
31 | contentlayer build [-c,--config #0] [--clearCache] [--verbose]
32 | Transforms your content into static data
33 |
34 | contentlayer dev [-c,--config #0] [--clearCache] [--verbose]
35 | Same as "contentlayer build" but with watch mode
36 |
37 | You can also print more details about any of these commands by calling them
38 | with the `-h,--help` flag right after the command name.
39 | ```
40 |
41 | ## Commands
42 |
43 | ### `build`
44 |
45 | ```txt
46 | contentlayer build [options]
47 | ```
48 |
49 | Transforms content into data objects that your pages and components can consume. These objects are written to files in the `.contentlayer/generated` directory.
50 |
51 | ### `dev`
52 |
53 | ```txt
54 | contentlayer dev [options]
55 | ```
56 |
57 | Runs a build, then listens for changes to your content files, rebuilding after each change.
58 |
59 | ## Options
60 |
61 | The following options are available to run with each command.
62 |
63 | ### `config` (alias: `-c`)
64 |
65 | Use a custom config file path. Both `contentlayer.config.ts` and `contentlayer.config.js` work by default.
66 |
67 | ### `clearCache`
68 |
69 | Clears the `.contentlayer/generated` directory before running the specified command.
70 |
71 | ### `verbose`
72 |
73 | Adds more detailed output when running a command.
74 |
75 | ### `help`
76 |
77 | Generates usage instructions and options for the CLI. Does not require an associated command.
78 |
--------------------------------------------------------------------------------
/content/docs/500-reference/200-next-contentlayer.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: e6e7eb3a
3 | title: next-contentlayer
4 | excerpt: Helper for improving the experience when developing with Next.js.
5 | ---
6 |
7 | The Next.js plugin provides helpers for improving the developer experience in using Contentlayer with Next.js.
8 |
9 | ## `withContentlayer`
10 |
11 | The `withContentlayer` utility automatically hooks Contentlayer into Next.js `build` and `dev` processes, which means you don't have to worry about working with the CLI when running or building your Next.js project.
12 |
13 | To enable it, add the following to your `next.config.js` file:
14 |
15 | ```js
16 | // next.config.js
17 |
18 | import { withContentlayer } from 'next-contentlayer'
19 |
20 | export default withContentlayer({})
21 | ```
22 |
23 | ## `useMDXComponent`
24 |
25 | If you're using MDX in your Next.js project, `useMDXComponent` makes it easy to render MDX in your layouts.
26 |
27 | Here is an example of a layout that receives a `slug `parameter, then uses it to retrieve a document and render MDX on the page.
28 |
29 | ```jsx
30 | import { useMDXComponent } from 'next-contentlayer/hooks'
31 | import { allDocs } from 'contentlayer/generated'
32 | import Button from '../components/Button'
33 |
34 | const mdxComponents = {
35 | Button,
36 | }
37 |
38 | export default function DocPage({ doc }) {
39 | const MDXContent = useMDXComponent(doc.body.code)
40 |
41 | return (
42 |
43 |
{doc.title}
44 |
45 |
46 | )
47 | }
48 |
49 | export const getStaticProps = ({ params: { slug } }) => {
50 | const doc = allDocs.find((doc) => doc._raw.flattenedPath === slug)
51 | return { props: { doc } }
52 | }
53 | ```
54 |
55 |
56 | Remember to set `contentType: 'mdx'` in your config for proper [processing of MDX files](/docs/sources/files/mdx).
57 |
58 |
59 | For a more holistic view of using MDX in a project, refer to [the example Next.js project](https://github.com/contentlayerdev/next-contentlayer-example).
60 |
61 | ## `createContentlayerPlugin`
62 |
63 | The `createContentlayerPlugin` function allows you to add non-default configuration options to the next-contentlayer plugin. This is not a common use. See [`withContentlayer`](#withContentlayer) for typical use.
64 |
65 | ```js
66 | import { createContentlayerPlugin } from 'next-contentlayer'
67 |
68 | const withContentlayer = createContentlayerPlugin({
69 | // Additional Contentlayer config options
70 | })
71 |
72 | export default withContentlayer({
73 | // Your Next.js config...
74 | })
75 | ```
76 |
--------------------------------------------------------------------------------
/content/docs/500-reference/200-source-files/300-define-nested-type.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: eeeb4ab5
3 | title: defineNestedType
4 | excerpt: Technical reference for defining a nested document type schema.
5 | ---
6 |
7 | `defineNestedType` defines the schema for a reusable shape of data that can be embedded within documents.
8 |
9 | Nested types are defined within document type definitions, and therefore do not have to be included in the [`makeSource` options](/docs/reference/source-files/make-source).
10 |
11 | ## Usage
12 |
13 | ```js
14 | const SEO = defineNestedType(() => ({
15 | name: 'SEO',
16 | fields: {
17 | title: {
18 | type: 'string',
19 | },
20 | },
21 | }))
22 |
23 | const Doc = defineDocumentType(() => ({
24 | name: 'Doc',
25 | filePathPattern: '**/*.md',
26 | fields: {
27 | // ...
28 | seo: {
29 | type: 'nested',
30 | of: SEO,
31 | },
32 | },
33 | }))
34 | ```
35 |
36 | ## Options
37 |
38 | ### `name` (required)
39 |
40 | Name of the document. This defines the types and functions that are generated for documents of this type.
41 |
42 | ```js
43 | const SEO = defineNestedType(() => ({
44 | name: "SEO",
45 | // ...
46 | })
47 | ```
48 |
49 | Because nested types are not documents, they are not exported as individual data files. But a type definition will still be generated (as `SEO` in the usage example).
50 |
51 | ### `fields`
52 |
53 | Field definitions determine the data shape for the document type. See [the field types reference](/docs/reference/source-files/field-types) for more information.
54 |
55 | ### `description`
56 |
57 | Provides a description for the generated type definition.
58 |
--------------------------------------------------------------------------------
/content/docs/500-reference/200-source-files/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: f4638f76
3 | title: '@contentlayer/source-files'
4 | excerpt: API reference for working with local files (Git CMS).
5 | collapsible: true
6 | collapsed: true
7 | ---
8 |
9 | This module provides the methods for working with local files as your content source.
10 |
11 | Typical usage involves three primary functions:
12 |
13 | - [`makeSource`](/docs/reference/source-files/make-source) controls the entire schema definition for your application.
14 | - [`defineDocumentType`](/docs/reference/source-files/define-document-type) defines the schema top-level document types (also known as _models_ or _content types_).
15 | - [`defineNestedType`](/docs/reference/source-files/define-nested-type) defines the schema for data shapes that can be reused across multiple document type definitions.
16 |
17 | ---
18 |
19 | ## Reference Guides
20 |
21 |
22 |
23 | These guides will dig into the details of each function to enable you to better understand how to work with local
24 | content.
25 |
26 |
27 | -
28 | -
29 | -
30 | -
31 |
32 |
33 |
34 | ---
35 |
36 | ## Other File Source Guides
37 |
38 |
39 |
40 | There are a few other guides to help you learn how to work with local content.
41 |
42 |
43 |
44 | -
45 | -
46 | -
47 | -
48 | -
49 |
50 |
51 |
--------------------------------------------------------------------------------
/content/docs/500-reference/250-source-notion/100-make-source.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: fa487097
3 | title: makeSource
4 | excerpt: makeSource provides Contentlayer with the schema and configuration when using local files as the content source.
5 | ---
6 |
7 | `makeSource` provides Contentlayer with the schema and configuration for your application.
8 |
9 | ## Usage
10 |
11 | The code calling `makeSource` should be placed in `contentlayer.config.js`.
12 |
13 | ```ts
14 | // contentlayer.config.js
15 |
16 | import { makeSource } from 'contentlayer-source-notion'
17 |
18 | export default makeSource({
19 | /* options */
20 | })
21 | ```
22 |
23 | ## Options
24 |
25 |
26 |
27 | ### `client`
28 |
29 |
30 |
31 | The `@notionhq/client` instance used to query the Notion API.
32 |
33 | **Example:**
34 |
35 | ```js
36 | import { Client } from '@notionhq/client'
37 | import { makeSource } from 'contentlayer-source-notion'
38 |
39 | const client = new Client({ auth: process.env.NOTION_TOKEN })
40 |
41 | export default makeSource({
42 | client,
43 | })
44 | ```
45 |
46 | This would use `process.env.NOTION_TOKEN` when calling the Notion API.
47 |
48 |
49 |
50 | ### `renderer`
51 |
52 |
53 |
54 | The `@notion-render/client` instance used to transform Notion Block into HTML.
55 |
56 | **Example:**
57 |
58 | ```js
59 | import { NotionRenderer } from '@notion-render/client'
60 | import { makeSource } from 'contentlayer-source-notion'
61 |
62 | const renderer = new NotionRenderer()
63 |
64 | export default makeSource({
65 | renderer
66 | })
67 | ```
68 |
69 |
70 |
71 | ### `databaseTypes`
72 | (required)
73 |
74 |
75 |
76 | Your databases definitions for your project. See [`defineDatabase`](/docs/reference/source-notion/define-database) for usage.
77 |
78 |
79 |
80 | ### `fieldOptions`
81 |
82 |
83 |
84 | Provides the ability to manipulate how fields are written when parsing the content source.
85 |
86 | **Options:**
87 |
88 | - `bodyFieldName` (default: `body`): Name of the field containing the body/content extracted when the body type is `markdown` or `mdx`.
89 | - `typeFieldName` (default: `type`): Name of the field containing the name of the document type.
90 |
91 | **Example:**
92 |
93 | ```js
94 | export default makeSource({
95 | fieldOptions: {
96 | bodyFieldName: 'content',
97 | typeFieldName: '__typename',
98 | },
99 | })
100 | ```
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/content/docs/500-reference/250-source-notion/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: c60229ba
3 | title: '@contentlayer/source-notion'
4 | excerpt: API reference for working with Notion.
5 | collapsible: true
6 | collapsed: true
7 | ---
8 |
9 | This module provides the methods for working with [Notion](https://notion.so) as your content source.
10 |
11 | Typical usage involves two primary functions:
12 |
13 | - [`makeSource`](/docs/reference/source-notion/make-source) controls the entire schema definition for your application.
14 | - [`defineDatabase`](/docs/reference/source-notion/define-database) defines how your database should be used to generate your document types.
15 |
16 | ---
17 |
18 | ## Reference Guides
19 |
20 |
21 |
22 | These guides will dig into the details of each function to enable you to better understand how to work with local
23 | content.
24 |
25 |
26 | -
27 | -
28 | -
29 |
30 |
31 |
32 | ---
33 |
34 | ## Other Notion Source Guides
35 |
36 |
37 |
38 | There are a few other guides to help you learn how to work with Notion.
39 |
40 |
41 |
42 | -
43 | -
44 | -
45 | -
46 | -
47 |
48 |
49 |
--------------------------------------------------------------------------------
/content/docs/500-reference/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: abcab6a5
3 | title: API Reference
4 | show_child_cards: true
5 | excerpt: Technical API reference for Contentlayer usage.
6 | ---
7 |
8 | Technical API reference for Contentlayer, the CLI, and associated plugins.
9 |
--------------------------------------------------------------------------------
/content/docs/600-integrations/100-stackbit/300-dev-server.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: f1dfe99b
3 | title: Stackbit Development Server
4 | nav_title: Development Server
5 | excerpt: Hook the Stackbit development server into Next.js and Contentlayer.
6 | ---
7 |
8 | The Stackbit development server is run using [the `stackbit dev` command](https://docs.stackbit.com/reference/stackbit-cli#dev).
9 |
10 | [`experimental-next-stackbit`](https://npmjs.com/package/experimental-next-stackbit) can be used in conjunction with [`next-contentlayer`](/docs/reference/next-contentlayer) to hook the [`contentlayer dev`](/docs/reference/cli#dev) and [`stackbit dev`](https://docs.stackbit.com/reference/stackbit-cli#dev) commands into the Next.js development server (`next dev`). See below for usage.
11 |
12 | ## `withStackbit`
13 |
14 | Next.js configuration, along with the [`withContentlayer` hook](/docs/reference/next-contentlayer#withcontentlayer) should be wrapped in the `withStackbit` method.
15 |
16 | ```js
17 | // next.config.js
18 |
19 | const { withContentlayer } = require('next-contentlayer')
20 | const { withStackbit } = require('experimental-next-stackbit')
21 |
22 | module.exports = withStackbit(
23 | withContentlayer({
24 | reactStrictMode: true,
25 | }),
26 | )
27 | ```
28 |
29 | This function will automatically detect if you are in production (when `process.env.NODE_ENV` is `production`) and skip running the Stackbit development server.
30 |
--------------------------------------------------------------------------------
/content/docs/600-integrations/100-stackbit/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: b1141d65
3 | title: Stackbit
4 | excerpt: Stackbit is a composable visual editor that supports multiple content sources and site frameworks.
5 | label: Experimental
6 | collapsible: true
7 | collapsed: true
8 | show_child_cards: true
9 | ---
10 |
11 | Stackbit is a composable visual editor that supports multiple content sources and site frameworks.
12 |
13 | 
14 |
15 | Contentlayer can be used in sites that are Stackbit-enabled to ease the process for developers working with content in their components and pages.
16 |
17 | Contentlayer provides a method for [transforming Stackbit configuration into Contentlayer configuration](/docs/integrations/stackbit/config), so you only have to define the shape of your content in one place.
18 |
19 | ## How it Works
20 |
21 | With the appropriate packages installed, the process works like this:
22 |
23 | 1. Define the shape of your documents using [Stackbit's `models` property](https://docs.stackbit.com/reference/config/content-modeling/models).
24 | 1. Import Stackbit config object into the Contentlayer configuration file.
25 | 1. Extend any necessary properties to support your code.
26 | 1. Configure the Contentlayer source.
27 |
28 | See below for an example, and also view [the API reference](/docs/integrations/stackbit/config) for specific usage details.
29 |
30 | ## Example Project
31 |
32 | If you'd like to see this in process, check out [this example project](https://github.com/stackbit-themes/stackbit-examples/tree/main/contentlayer), which integrates Stackbit and Contentlayer in a Next.js site.
33 |
34 | The README file has setup information, but we recommend reading through the tutorial below to understand how the pieces fit together.
35 |
36 | ## Stackbit Guides
37 |
38 | The following docs will get you started integrating Stackbit and Contentlayer into your Next.js project.
39 |
--------------------------------------------------------------------------------
/content/docs/600-integrations/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: ed449e02
3 | title: Integrations
4 | excerpt: Connections to other services that are not sources or frameworks
5 | show_child_cards: true
6 | ---
7 |
8 | Contentlayer has additional integrations that are not specific to content sources or site frameworks. These guides are shown below.
9 |
--------------------------------------------------------------------------------
/content/docs/700-other/100-faq.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: e58c2f47
3 | title: Frequently Asked Questions
4 | nav_title: FAQ
5 | excerpt: Answers to the most common questions about Contentlayer.
6 | ---
7 |
8 | ## What problem is Contentlayer solving?
9 |
10 | Modern web frameworks don't prescribe a method for parsing content. They provide powerful page routing and rendering processes, but it's up to you to provide it with content.
11 |
12 | That's a lot of work. Without the time and energy to build a truly great processing mechanism, it's easy to quickly degrade the developer experience these tools have afforded us.
13 |
14 | Contentlayer persists the great developer experience provided by modern web frameworks by making it easy to work with content in your web project.
15 |
16 | ## Why is Contentlayer fast?
17 |
18 | Contentlayer leverages optimizations of build tools to the fullest to make processing source content a breeze.
19 |
20 | It then caches that content intelligently and builds incrementally. When you update content, Contentlayer will only build what has changed, taking advantage of work already done.
21 |
22 | ## Can I use Contentlayer with my existing tools?
23 |
24 | Contentlayer is built to be framework agnostic. Contentlayer is a content processor at its core, but provides modules for importing content from various sources, and uses plugins to provide tight integration with modern frameworks.
25 |
26 | Our docs list [current supported sources](/docs/sources) and [frameworks](/docs/environments). And if you don't see what you're looking for, [start a discussion](https://github.com/contentlayerdev/contentlayer/issues/new). We're constantly looking for new use cases to consider.
27 |
28 | ## Next.js already supports MDX. Why do I need Contentlayer?
29 |
30 | It is a misconception that Next.js inherently supports MDX. Next.js makes no inference whatsoever about where your content lives and how it should be processed.
31 |
32 | Libraries like [mdx-bundler](https://github.com/kentcdodds/mdx-bundler) and [next-mdx-remote](https://github.com/hashicorp/next-mdx-remote) provide tooling to help in processing MDX. In addition to processing MDX, Contentlayer also parses and validates content and provides auto-generated TypeScript type definitions. And it does so while prioritizing performance and developer experience.
33 |
34 | Learn more about [how Contentlayer compares to other content processors](/docs/concepts/comparison#content-processors).
35 |
--------------------------------------------------------------------------------
/content/docs/700-other/200-roadmap.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: ce0cb3aa
3 | title: Roadmap
4 | excerpt: Get a picture of where we're going with upcoming releases.
5 | ---
6 |
7 | [See here](https://stackbit.notion.site/Contentlayer-Roadmap-ca6d21d6a1424b11b11692d6356c1ff7) for our roadmap.
8 |
9 | This list is not exhaustive, but meant to provide visibility into our high-level priorities.
10 |
11 | ## Adding Items to Roadmap
12 |
13 | To add something to the roadmap, first check [issues on GitHub](https://github.com/contentlayerdev/contentlayer/issues) for a relevant topic. Feel free to comment to ask for an updated status.
14 |
15 | If you don't see a relevant topic, [create one](https://github.com/contentlayerdev/contentlayer/issues/new).
16 |
17 | ## Roadmap States
18 |
19 | The roadmap shows each item in one of four states:
20 |
21 | - **Considering:** We're actively talking about the topic, but have not yet prioritized it.
22 | - **Planned:** We plan to work on these items as we release those currently in progress. These items are subject to change.
23 | - **In Progress:** Work is being done against the topic.
24 | - **Released:** The feature/change has been released in a new version.
25 |
--------------------------------------------------------------------------------
/content/docs/700-other/300-changelog.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: eec1d8c0
3 | title: Changelog
4 | excerpt: Information on detailed notes for previous releases
5 | ---
6 |
7 | When we release a new version, we notify [the Discord community](https://discord.gg/rytFErsARm) and link to [detailed release notes](https://github.com/contentlayerdev/contentlayer/releases) in GitHub.
8 |
--------------------------------------------------------------------------------
/content/docs/700-other/400-contributing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: d739035b
3 | title: Contributing
4 | excerpt: Multiple ways to get more involved with Contentlayer.
5 | ---
6 |
7 | There are multiple ways that you can get involved to help make Contentlayer the best way to work with content in your front end applications:
8 |
9 | - Using Contentlayer
10 | - Providing Support
11 | - Promoting Contentlayer
12 | - Building Examples
13 | - Writing Documentation
14 | - Contributing Code
15 |
16 | ## Using Contentlayer
17 |
18 | The easiest and most valuable way for you to contribute to Contentlayer is **to use it, provide feedback, and report bugs.**
19 |
20 | For feedback and general conversation, [join our Discord community](https://discord.gg/rytFErsARm). To report bugs, [open an issue on GitHub](https://github.com/contentlayerdev/contentlayer/issues/new).
21 |
22 | ## Providing Support
23 |
24 | As Contentlayer's usage grows, we'll need help keeping up with [issues on GitHub](https://github.com/contentlayerdev/contentlayer/issues). We welcome community members helping to confirm bug reports, while also adding their use cases and perspectives to feature requests.
25 |
26 | ## Promoting Contentlayer
27 |
28 | If you love Contentlayer, tell the world! Tweet about it. Write a blog post. Bring it up in a planning meeting for your next web project. Whatever it is, the more you talk about Contentlayer, the better Contentlayer becomes.
29 |
30 | ## Building Examples
31 |
32 | We have a small and focused set of examples right now. We aim to expand on these, but want to do so thoughtfully. Too many examples can be distracting for users and difficult to maintain for builders.
33 |
34 | If you'd like to build a new example or think you have a helpful one, get in touch. Send a message in #contributing on Discord, or message `seancdavis29#9241` directly, and we'll find the right example for you.
35 |
36 | ## Writing Documentation
37 |
38 | The docs are off to a good start but have a long way to go. If you are interested in writing documentation, please DM `seancdavis29#9241` on Discord.
39 |
40 | ## Contributing Code
41 |
42 | If you're interested in contributing code, please either [open an issue](https://github.com/contentlayerdev/contentlayer/issues/new) or find an issue on which you'd like to work. Add a comment stating that you'd like to work on resolving the issue, and we'll make a plan together, as the codebase is complex.
43 |
--------------------------------------------------------------------------------
/content/docs/700-other/500-known-problems.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: cdffa1e4
3 | title: Known Problems
4 | excerpt: Open bugs and other known issues.
5 | ---
6 |
7 | We track all known problems with [GitHub issues](https://github.com/contentlayerdev/contentlayer/issues).
8 |
9 | Those that have confirmed as problems are identified with the `bug` label. Here is [the list of open bugs](https://github.com/contentlayerdev/contentlayer/issues?q=is%3Aissue+is%3Aopen+label%3Abug).
10 |
11 | ## Common Problems
12 |
13 | Among the issues referenced above, these are the problems most commonly encountered by Contentlayer users, along with links to the appropriate GitHub discussion(s)/issue(s):
14 |
15 | - ESLint error: `Unable to resolve path to module 'next-contentlayer'` ([GitHub Issue](https://github.com/contentlayerdev/contentlayer/issues/48#issuecomment-1097753073))
16 | - `useLivereload` hook is needed in some cases ([GitHub Issue](https://github.com/contentlayerdev/contentlayer/issues/135))
17 |
--------------------------------------------------------------------------------
/content/docs/700-other/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: b3a6b480
3 | title: Other
4 | show_child_cards: true
5 | excerpt: A collection of other resources to help you learn more, stay up to date, and contribute.
6 | ---
7 |
8 | See below for a collection of resources to help you learn more, stay up to date, and contribute to Contentlayer.
9 |
--------------------------------------------------------------------------------
/content/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | global_id: '' # Index page doesn't use a global ID
3 | title: Contentlayer Documentation
4 | nav_title: Documentation
5 | excerpt: Contentlayer is a content preprocessor that validates and transforms your content into type-safe JSON you can easily import into your application.
6 | ---
7 |
8 | Contentlayer is a content preprocessor that validates and transforms your content into type-safe JSON you can easily import into your application.
9 |
10 |
11 |
17 |
23 |
24 |
25 | ---
26 |
27 | ## Sources
28 |
29 |
30 |
31 | Contentlayer lets you choose the source for your content. Currently only local file source is officially supported.
32 |
33 |
34 |
35 | Sources that are supported, planned, or being considered:
36 |
37 | -
38 | -
39 | -
40 | -
41 |
42 |
43 |
44 |
45 | ---
46 |
47 | ## Environments
48 |
49 |
50 |
51 | Contentlayer supports a tight integration with Next.js. Other environment support is coming soon.
52 |
53 |
54 |
55 |
56 | Frameworks that are supported, planned, or being considered:
57 |
58 | -
59 | -
60 | -
61 | -
62 |
63 |
64 |
65 |
66 | ---
67 |
68 | ## Reference
69 |
70 |
71 |
72 | Technical API reference for Contentlayer, the CLI, and associated plugins.
73 |
74 |
75 |
76 |
77 | -
78 | -
79 | -
80 |
81 |
82 |
--------------------------------------------------------------------------------
/content/examples/100-nextjs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Next.js
3 | excerpt: Next.js
4 | github_repo: contentlayerdev/next-contentlayer-example
5 | open_file: posts/change-me.md
6 | ---
7 |
--------------------------------------------------------------------------------
/content/examples/200-vite.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Vite
3 | excerpt: Coming soon
4 | label: Coming soon
5 | ---
6 |
7 | A Vite example is coming soon
8 |
--------------------------------------------------------------------------------
/content/examples/900-other.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Other Examples
3 | excerpt: Various Contentlayer Examples
4 | ---
5 |
6 | - [Contentlayer Website / Docs](https://github.com/contentlayerdev/website): The Contentlayer website & documentation is built with Contentlayer as well. It might be a good example for advanced usage patterns.
7 |
--------------------------------------------------------------------------------
/content/examples/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Examples
3 | excerpt: Contentlayer Examples
4 | ---
5 |
--------------------------------------------------------------------------------
/contentlayer.config.ts:
--------------------------------------------------------------------------------
1 | // TODO remove eslint-disable when fixed https://github.com/import-js/eslint-plugin-import/issues/1810
2 | // eslint-disable-next-line import/no-unresolved
3 | import { makeSource } from '@contentlayer/source-files'
4 | import highlight from 'rehype-highlight'
5 | import { contentDirPath } from './src/contentlayer/utils'
6 | import { validateDuplicateIds } from './src/utils/validate-duplicate-ids'
7 | import * as documentTypes from './src/contentlayer'
8 |
9 | export default makeSource({
10 | contentDirPath,
11 | documentTypes,
12 | mdx: { rehypePlugins: [highlight] },
13 | onSuccess: async (importData) => {
14 | const { allDocs } = await importData()
15 | await validateDuplicateIds(allDocs)
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | command = "npm run build"
3 | publish = ".next"
4 |
5 | [[plugins]]
6 | package = "@netlify/plugin-nextjs"
7 |
--------------------------------------------------------------------------------
/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-sitemap.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | siteUrl: 'https://contentlayer.dev',
3 | generateRobotsTxt: true,
4 | }
5 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const { withContentlayer } = require('next-contentlayer')
3 |
4 | module.exports = withContentlayer({
5 | images: {
6 | domains: ['pbs.twimg.com', 'avatars.githubusercontent.com', 'i.imgur.com'],
7 | },
8 | headers: async () => [
9 | {
10 | source: '/:path*',
11 | headers: [
12 | { key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
13 | { key: 'Cross-Origin-Embedder-Policy', value: 'same-origin' },
14 | ],
15 | },
16 | ],
17 | })
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "contentlayer-docs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3001 & local-ssl-proxy --source 3000 --target 3001",
7 | "build": "contentlayer build && next build && next-sitemap",
8 | "start": "next start",
9 | "check-links": "blc \"http://localhost:3001\" -roe"
10 | },
11 | "dependencies": {
12 | "@notionhq/client": "^2.2.5",
13 | "@radix-ui/react-dropdown-menu": "^0.1.6",
14 | "@radix-ui/react-scroll-area": "^0.1.4",
15 | "@radix-ui/react-tabs": "^0.1.5",
16 | "@radix-ui/react-tooltip": "^0.1.7",
17 | "@stackblitz/sdk": "^1.7.0-alpha.3",
18 | "@tailwindcss/typography": "^0.5.2",
19 | "cheerio": "^1.0.0-rc.10",
20 | "classnames": "^2.3.1",
21 | "contentlayer-source-notion": "^0.0.1-alpha.26",
22 | "date-fns": "^2.28.0",
23 | "kbar": "^0.1.0-beta.34",
24 | "markdown-to-jsx": "^7.1.6",
25 | "next": "12.1.5",
26 | "next-sitemap": "^2.5.20",
27 | "react": "18.0.0",
28 | "react-dom": "18.0.0",
29 | "react-helmet": "^6.1.0",
30 | "shiki-twoslash": "^3.0.2",
31 | "tailwindcss-radix": "^1.6.0"
32 | },
33 | "devDependencies": {
34 | "@netlify/plugin-nextjs": "^4.41.3",
35 | "@types/eslint": "^8.4.1",
36 | "@types/postcss-import": "^14.0.0",
37 | "@types/react": "^18.0.3",
38 | "@types/react-helmet": "^6.1.5",
39 | "autoprefixer": "^10.4.4",
40 | "broken-link-checker": "^0.7.8",
41 | "contentlayer": "^0.3.3",
42 | "eslint": "8.13.0",
43 | "eslint-config-next": "12.1.5",
44 | "eslint-plugin-import": "^2.26.0",
45 | "gray-matter": "^4.0.3",
46 | "local-ssl-proxy": "^1.3.0",
47 | "mdast-util-mdx": "^2.0.0",
48 | "mdast-util-to-markdown": "^1.3.0",
49 | "next-contentlayer": "^0.3.3",
50 | "postcss": "^8.4.12",
51 | "postcss-import": "^14.1.0",
52 | "prettier": "^2.6.2",
53 | "prettier-plugin-tailwindcss": "^0.1.8",
54 | "rehype-highlight": "^5.0.2",
55 | "tailwindcss": "^3.0.24",
56 | "typescript": "4.9.4"
57 | },
58 | "packageManager": "yarn@3.2.0",
59 | "resolutions": {
60 | "@stackblitz/sdk@1.7.0": "patch:@stackblitz/sdk@npm:1.7.0-alpha.3#.yarn/patches/@stackblitz-sdk-npm-1.7.0-alpha.3-1f8676ad43.patch"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | 'postcss-import': {},
4 | 'tailwindcss/nesting': {},
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-167x167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-167x167.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/public/favicon/favicon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/favicon-128x128.png
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-196x196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/favicon-196x196.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/favicon-96x96.png
--------------------------------------------------------------------------------
/public/favicon/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/mstile-144x144.png
--------------------------------------------------------------------------------
/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/public/favicon/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/mstile-310x150.png
--------------------------------------------------------------------------------
/public/favicon/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/mstile-310x310.png
--------------------------------------------------------------------------------
/public/favicon/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/favicon/mstile-70x70.png
--------------------------------------------------------------------------------
/public/fonts/virgil.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/fonts/virgil.woff2
--------------------------------------------------------------------------------
/public/images/beta-launch-post-meta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/beta-launch-post-meta.png
--------------------------------------------------------------------------------
/public/images/content-as-data.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/content-as-data.png
--------------------------------------------------------------------------------
/public/images/content-is-hard-meta.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/content-is-hard-meta.png
--------------------------------------------------------------------------------
/public/images/content-timeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/content-timeline.png
--------------------------------------------------------------------------------
/public/images/contentlayer-in-five-minutes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/contentlayer-in-five-minutes.png
--------------------------------------------------------------------------------
/public/images/intro-thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/intro-thumbnail.jpg
--------------------------------------------------------------------------------
/public/images/local-data-transformation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/local-data-transformation.png
--------------------------------------------------------------------------------
/public/images/logos/astro.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/images/logos/contentful.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/images/logos/mdx.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/public/images/logos/notion.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/public/images/logos/remix.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/public/images/logos/sanity.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/public/images/logos/vite.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/images/notion-contentlayer-source.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/notion-contentlayer-source.png
--------------------------------------------------------------------------------
/public/images/notion/database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/notion/database.png
--------------------------------------------------------------------------------
/public/images/notion/integration_granular_permissions.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/notion/integration_granular_permissions.gif
--------------------------------------------------------------------------------
/public/images/performance-comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/performance-comparison.png
--------------------------------------------------------------------------------
/public/images/playground-hint-dev-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/playground-hint-dev-server.png
--------------------------------------------------------------------------------
/public/images/playground-hint-edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/playground-hint-edit.png
--------------------------------------------------------------------------------
/public/images/playground-hint-updates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/playground-hint-updates.png
--------------------------------------------------------------------------------
/public/images/post-feed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/post-feed.png
--------------------------------------------------------------------------------
/public/images/post-layout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/post-layout.png
--------------------------------------------------------------------------------
/public/images/stackbit-visual-editing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/contentlayerdev/website/861e79d100ebcc41de89d8bcb5d522b37dcf46b0/public/images/stackbit-visual-editing.gif
--------------------------------------------------------------------------------
/scripts/generate-page-ids.mjs:
--------------------------------------------------------------------------------
1 | import crypto from 'crypto'
2 | import glob from 'glob'
3 | import fs from 'fs'
4 | import matter from 'gray-matter'
5 |
6 | const pages = glob.sync('./content/docs/**/*.md*', { ignore: './content/docs/index.md*' })
7 |
8 | function generateId() {
9 | const id = crypto.randomBytes(4).toString('hex')
10 | if (id.match(/^\d/)) return generateId()
11 | return id
12 | }
13 |
14 | const skipped = []
15 |
16 | pages.forEach((pagePath) => {
17 | const id = generateId()
18 | const rawContent = fs.readFileSync(pagePath, 'utf8')
19 | const { data } = matter(rawContent)
20 | if (data.global_id) {
21 | skipped.push(pagePath)
22 | return
23 | }
24 | if (!rawContent.startsWith('---\n')) {
25 | throw new Error(`[Error] ${pagePath} does not have frontmatter`)
26 | }
27 |
28 | const newContent = rawContent.replace(/^---\n/, `---\nglobal_id: ${id}\n`)
29 | fs.writeFileSync(pagePath, newContent)
30 | console.log(`[New ID] ${pagePath} -> ${id}`)
31 | })
32 |
33 | if (skipped.length) {
34 | console.log(`[Skipped] ${skipped.length} with existing ids`)
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/ColorSchemeContext.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | FC,
3 | useEffect,
4 | Dispatch,
5 | SetStateAction,
6 | createContext,
7 | useState,
8 | useContext,
9 | useMemo,
10 | useCallback,
11 | } from 'react'
12 | import { ColorScheme } from '../utils/syntax-highlighting'
13 |
14 | const ColorSchemeContext = createContext<'light' | 'dark' | 'system'>('light')
15 | const UpdateColorSchemeContext = createContext<(colorScheme: 'light' | 'dark' | 'system') => void>(() => {})
16 |
17 | export const useColorScheme = () => useContext(ColorSchemeContext)
18 | export const useUpdateColorScheme = () => useContext(UpdateColorSchemeContext)
19 |
20 | export const ColorSchemeProvider: FC> = ({ children }) => {
21 | const initalColorScheme = useMemo(
22 | () =>
23 | typeof window !== 'undefined' ? (localStorage.getItem('theme') as ColorScheme | null) ?? 'system' : 'system',
24 | [],
25 | )
26 | const [colorScheme, setColorScheme] = useState<'light' | 'dark' | 'system'>(initalColorScheme)
27 |
28 | const updateColorScheme = useCallback(
29 | (newColorScheme: 'light' | 'dark' | 'system') => {
30 | if (newColorScheme === colorScheme) return
31 |
32 | setColorScheme(newColorScheme)
33 |
34 | if (newColorScheme === 'system') {
35 | if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
36 | document.documentElement.classList.add('dark')
37 | } else {
38 | document.documentElement.classList.remove('dark')
39 | }
40 | localStorage.removeItem('theme')
41 | } else {
42 | if (newColorScheme === 'dark') {
43 | document.documentElement.classList.add('dark')
44 | } else {
45 | document.documentElement.classList.remove('dark')
46 | }
47 | localStorage.theme = newColorScheme
48 | }
49 | },
50 | [colorScheme],
51 | )
52 |
53 | useEffect(() => {
54 | if (typeof window === 'undefined') return
55 | if (colorScheme === 'system') {
56 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => updateColorScheme('system'))
57 | }
58 | }, [colorScheme, updateColorScheme])
59 |
60 | return (
61 |
62 | {children}
63 |
64 | )
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/blog/BenchmarkResults.tsx:
--------------------------------------------------------------------------------
1 | const TableHeadCell: React.FC<
2 | React.PropsWithChildren<{
3 | style?: React.CSSProperties
4 | className?: string
5 | }>
6 | > = ({ children, className, style }) => {
7 | return (
8 |
9 | {children}
10 |
11 | )
12 | }
13 |
14 | const TableCell: React.FC<
15 | React.PropsWithChildren<{
16 | style?: React.CSSProperties
17 | className?: string
18 | }>
19 | > = ({ children, className, style }) => {
20 | return (
21 |
22 | {children}
23 |
24 | )
25 | }
26 |
27 | const ResultCell: React.FC> = ({ children, success }) => {
28 | let classes = `font-mono`
29 | if (success) classes += ` font-bold text-green-500`
30 |
31 | return {children}
32 | }
33 |
34 | export const BenchmarkResults: React.FC = () => {
35 | return (
36 |
37 |
38 |
39 |
40 |
41 |
42 | Cold Build
43 | Cached Build
44 |
45 |
46 |
47 |
48 | Next.js + Contentlayer
49 | 25.73 s
50 | 16.29 s
51 |
52 |
53 | Next.js + DIY Content
54 | 44.48 s
55 | 39.27 s
56 |
57 |
58 | Gatsby
59 | 46.59 s
60 | 25.73 s
61 |
62 |
63 |
64 |
65 |
76 |
77 | )
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/blog/BlogDetails.tsx:
--------------------------------------------------------------------------------
1 | import { Post } from 'contentlayer/generated'
2 | import { FC } from 'react'
3 | import Link from 'next/link'
4 | import { Icon } from 'src/components/common/Icon'
5 | import { format } from 'date-fns'
6 |
7 | export const BlogDetails: FC<{ post: Post; className?: string }> = ({ post, className }) => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | {format(new Date(post.date), 'MMMM dd, yyyy')}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/blog/BlogHeader.tsx:
--------------------------------------------------------------------------------
1 | import { Post } from 'contentlayer/generated'
2 | import { FC, useEffect, useState } from 'react'
3 | import { BlogDetails } from 'src/components/blog/BlogDetails'
4 | import Link from 'next/link'
5 | import { Icon } from 'src/components/common/Icon'
6 | import Image from 'next/image'
7 | import { format } from 'date-fns'
8 |
9 | export const BlogHeader: FC<{ post: Post }> = ({ post }) => {
10 | const [top, setTop] = useState(true)
11 |
12 | useEffect(() => {
13 | const handleScroll = () => setTop(window.scrollY <= 50)
14 | handleScroll()
15 | window.addEventListener('scroll', handleScroll)
16 | return () => {
17 | window.removeEventListener('scroll', handleScroll)
18 | }
19 | }, [])
20 |
21 | return (
22 | <>
23 |
24 |
25 |
26 | {post.title}
27 |
28 |
29 |
30 |
31 |
32 | {format(new Date(post.date), 'MMMM dd, yyyy')}
33 |
34 |
35 |
36 |
41 |
42 |
43 |
44 |
Blog
45 |
46 |
47 |
48 |
49 |
{post.title}
50 |
51 |
52 |
53 | >
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/blog/BlogPreview.tsx:
--------------------------------------------------------------------------------
1 | import { Post } from 'contentlayer/generated'
2 | import { BlogDetails } from 'src/components/blog/BlogDetails'
3 | import { Heading } from '../../components/landing-page/Heading'
4 | import { Paragraph } from '../../components/landing-page/Paragraph'
5 | import { ChevronLink } from '../../components/common/ChevronLink'
6 | import { FC } from 'react'
7 | import Image from 'next/image'
8 | import { Card } from '../common/Card'
9 | import Link from 'next/link'
10 | import { Icon } from 'src/components/common/Icon'
11 | import { format } from 'date-fns'
12 |
13 | export const BlogPreview: FC<{ post: Post }> = ({ post }) => {
14 | return (
15 |
16 |
30 |
31 |
32 | {post.title}
33 |
34 |
35 |
36 |
37 |
38 |
39 | {format(new Date(post.date), 'MMMM dd, yyyy')}
40 |
41 |
42 |
43 |
44 |
45 |
46 | {post.authors.map(({ name }, index) => (
47 |
48 | {index > 0 && ', '}
49 | {name}
50 |
51 | ))}
52 |
53 |
54 |
55 |
{post.excerpt}
56 |
57 |
58 |
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/blog/BulletList.tsx:
--------------------------------------------------------------------------------
1 | import { Icon } from '../common/Icon'
2 |
3 | export const BulletList: React.FC> = ({ columns, children }) => {
4 | return
5 | }
6 |
7 | export const BulletListItem: React.FC> = ({ type, children }) => {
8 | return (
9 |
10 | {type === 'check' && (
11 |
12 |
13 |
14 | )}
15 | {type === 'cross' && (
16 |
17 |
18 |
19 | )}
20 | {children}
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/blog/Playground.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, useEffect, useRef } from 'react'
2 | import stackblitz, { type VM } from '@stackblitz/sdk'
3 |
4 | export const Playground: FC<{ githubRepo: string; openFile?: string | '' }> = ({ githubRepo, openFile }) => {
5 | const ref = useRef(null)
6 | const [vm, setVm] = useState(undefined)
7 |
8 | useEffect(() => {
9 | if (ref.current && vm === undefined) {
10 | stackblitz
11 | .embedGithubProject(ref.current, 'contentlayerdev/next-contentlayer-example', {
12 | height: 700,
13 | showSidebar: true,
14 | openFile: openFile,
15 | })
16 | .then((_) => setVm(_))
17 | }
18 | }, [ref, openFile, vm])
19 |
20 | return (
21 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/blog/RelatedPosts.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import { allPosts, Post, RelatedPost } from 'contentlayer/generated'
3 | import { Card } from '../common/Card'
4 | import { BlogPreview } from './BlogPreview'
5 |
6 | export const RelatedPosts: FC<{ posts: RelatedPost[] }> = ({ posts }) => {
7 | return (
8 |
9 |
Related Posts
10 |
11 | {posts.map(({ slug }, index) => {
12 | const post = allPosts.find((_) => _.slug === slug)!
13 | return
14 | })}
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/blog/TLDR.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from '../common/Card'
2 | import { Icon } from '../common/Icon'
3 |
4 | export const TLDR: React.FC> = ({ children }) => {
5 | return (
6 |
7 | {children}
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/common/Arrow/CurvedLong.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ArrowCurvedLong: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Arrow/CurvedShort.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ArrowCurvedShort: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Arrow/StraightDashed.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ArrowStraightDashed: FC = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/Arrow/StraightLong.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ArrowStraightLong: FC = () => {
4 | return (
5 |
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/common/Arrow/StraightShort.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ArrowStraightShort: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Arrow/index.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | import { ArrowStraightLong } from './StraightLong'
4 | import { ArrowStraightShort } from './StraightShort'
5 | import { ArrowStraightDashed } from './StraightDashed'
6 | import { ArrowCurvedLong } from './CurvedLong'
7 | import { ArrowCurvedShort } from './CurvedShort'
8 | import { ArrowLoopedLong } from './LoopedLong'
9 | import { ArrowLoopedShort } from './LoopedShort'
10 |
11 | export type ArrowType =
12 | | 'straight-long'
13 | | 'straight-short'
14 | | 'straight-dashed'
15 | | 'curved-long'
16 | | 'curved-short'
17 | | 'looped-long'
18 | | 'looped-short'
19 |
20 | const arrows = {
21 | 'straight-long': ArrowStraightLong,
22 | 'straight-short': ArrowStraightShort,
23 | 'straight-dashed': ArrowStraightDashed,
24 | 'curved-long': ArrowCurvedLong,
25 | 'curved-short': ArrowCurvedShort,
26 | 'looped-long': ArrowLoopedLong,
27 | 'looped-short': ArrowLoopedShort,
28 | }
29 |
30 | export const Arrow: FC<{ type: ArrowType; className: string }> = ({ type, className }) => {
31 | const Arrow = arrows[type]
32 | return (
33 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/common/Author.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import Image from 'next/image'
3 | import Link from 'next/link'
4 |
5 | export const Author: FC<{ name: string; handle: string; avatar: string }> = ({ name, handle, avatar }) => {
6 | return (
7 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/common/Button.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import classnames from 'classnames'
3 | import { Icon, IconName } from './Icon'
4 | import Link from 'next/link'
5 | import { isExternalUrl } from '../../utils/helpers'
6 |
7 | const themeClasses = {
8 | primary:
9 | 'bg-violet-600 text-violet-50 border-violet-800 hover:bg-violet-500 dark:bg-violet-600 dark:border-violet-700 dark:hover:bg-violet-500 dark:hover:border-violet-600',
10 | secondary:
11 | 'bg-violet-100 text-violet-800 border-violet-200 hover:bg-violet-50 dark:text-violet-300 dark:border-violet-500/30 dark:hover:bg-violet-500/30 dark:bg-violet-500/20',
12 | }
13 |
14 | export const Button: FC<{
15 | label: string
16 | action?: () => void
17 | theme?: 'primary' | 'secondary'
18 | href?: string
19 | icon?: IconName
20 | }> = ({ label, action, href, theme = 'primary', icon }) => {
21 | const sharedClasses =
22 | 'px-6 py-2 flex justify-center items-center space-x-3 rounded-md border font-medium focus:outline-none focus:ring-2 focus:ring-violet-300 dark:focus:ring-violet-900'
23 |
24 | if (href) {
25 | return (
26 |
27 |
34 | {label}
35 | {icon && (
36 |
37 |
38 |
39 | )}
40 |
41 |
42 | )
43 | } else {
44 | return (
45 |
46 | {label}
47 | {icon && (
48 |
49 |
50 |
51 | )}
52 |
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/common/Callout.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react'
2 | import { Icon } from './Icon'
3 |
4 | export const Callout: React.FC<{ children: ReactNode; className?: string | '' }> = ({ children, className }) => {
5 | return (
6 |
9 |
10 |
11 |
12 |
13 |
{children}
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/common/Card.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react'
2 | import classNames from 'classnames'
3 |
4 | export const Card: FC<{ children: ReactNode; className?: string; shadow?: boolean; dark?: boolean }> = ({
5 | children,
6 | className,
7 | shadow = false,
8 | dark = false,
9 | }) => {
10 | return (
11 |
19 | {children}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/common/ChevronLink.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import Link from 'next/link'
3 | import { Icon } from './Icon'
4 |
5 | const isExternalUrl = (link: string): boolean => !link.startsWith('/')
6 |
7 | export const ChevronLink: FC<{ label: string; url: string }> = ({ label, url }) => {
8 | if (isExternalUrl(url)) {
9 | return (
10 |
16 | {label}
17 |
18 |
19 |
20 |
21 | )
22 | } else {
23 | return (
24 |
25 |
26 | {label}
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/common/Container.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import Head from 'next/head'
3 | import { useRouter } from 'next/router'
4 | import { SearchProvider } from '../SearchContext'
5 | import { MainNavigation } from './MainNavigation'
6 | import { Footer } from './Footer'
7 |
8 | export const Container: FC = ({ children, ...customMeta }) => {
9 | const router = useRouter()
10 |
11 | const baseUrl = `https://www.contentlayer.dev`
12 |
13 | const meta = {
14 | title: 'Contentlayer makes content easy for developers',
15 | description:
16 | 'Contentlayer is a content SDK that validates and transforms your content into type-safe JSON data you can easily import into your application.',
17 | url: customMeta.urlPath ? `${baseUrl}${customMeta.urlPath}` : baseUrl,
18 | name: 'Contentlayer',
19 | image: customMeta.imagePath ? `${baseUrl}${customMeta.imagePath}` : `${baseUrl}/images/beta-launch-post-meta.png`,
20 | type: 'website',
21 | ...customMeta,
22 | }
23 | const jsonLd = {
24 | '@context': 'http://www.schema.org',
25 | '@type': 'WebSite',
26 | name: meta.name,
27 | url: meta.url,
28 | }
29 |
30 | return (
31 | <>
32 |
33 | {meta.title}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | {children}
54 |
55 |
56 |
57 |
58 | >
59 | )
60 | }
61 |
--------------------------------------------------------------------------------
/src/components/common/Headings.tsx:
--------------------------------------------------------------------------------
1 | import { sluggifyTitle, getNodeText } from 'src/utils/sluggify'
2 |
3 | export const H2: React.FC> = ({ children }) => {
4 | const slug = sluggifyTitle(getNodeText(children))
5 | return (
6 | (window.location.hash = `#${slug}`)} className="group cursor-pointer">
7 | #
8 | {children}
9 |
10 | )
11 | }
12 |
13 | export const H3: React.FC> = ({ children }) => {
14 | const slug = sluggifyTitle(getNodeText(children))
15 | return (
16 | (window.location.hash = `#${slug}`)} className="group cursor-pointer">
17 | #
18 | {children}
19 |
20 | )
21 | }
22 |
23 | export const H4: React.FC> = ({ children }) => {
24 | const slug = sluggifyTitle(getNodeText(children))
25 | return (
26 | (window.location.hash = `#${slug}`)} className="group cursor-pointer">
27 | #
28 | {children}
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/common/Icon/API.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ApiIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Bars.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const BarsIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/BrokenLink.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const BrokenLinkIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Calendar.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CalendarIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Check.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CheckIcon: FC = () => {
4 | return (
5 |
11 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/common/Icon/CheckCircle.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CheckCircleIcon: FC = () => {
4 | return (
5 |
6 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/Icon/CheckCircleOutline.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CheckCircleOutlineIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/ChevronDown.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ChevronDownIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/ChevronLeft.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ChevronLeftIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/ChevronRight.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ChevronRightIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Close.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CloseIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Code.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CodeIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/CodeLight.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CodeLightIcon: FC = () => {
4 | return (
5 |
6 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Collapse.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CollapseIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Contentful.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ContentfulIcon: FC = () => {
4 | return (
5 |
6 |
10 |
14 |
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Contentlayer.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ContentlayerIcon: FC = () => {
4 | return (
5 |
6 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/Icon/CrossCircleOutline.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const CrossCircleOutlineIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Database.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const DatabaseIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Discord.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const DiscordIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Exclamation.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ExclamationIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Expand.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ExpandIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/ExternalLink.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const ExternalLinkIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Gear.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const GearIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/GitHub.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const GitHubIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Gitpod.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const GitpodIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/GraphQL.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const GraphQLIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Info.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const InfoIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Lightning.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const LightningIcon: FC = () => {
4 | return (
5 |
6 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Markdown.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const MarkdownIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Moon.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const MoonIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Notion.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const NotionIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/PlayButton.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const PlayButtonIcon: FC = () => {
4 | return (
5 |
6 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Plus.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const PlusIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Question.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const QuestionIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Rocket.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const RocketIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Search.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const SearchIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Sign.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const SignIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Sun.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const SunIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/Users.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const UsersIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/common/Icon/WordPress.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const WordPressIcon: FC = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/common/Label.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const Label: FC<{ text: string; theme?: 'default' | 'primary' }> = ({ text, theme = 'default' }) => {
4 | return (
5 |
12 | {text}
13 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/common/Link.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react'
2 | import NextLink from 'next/link'
3 | import { Icon } from './Icon'
4 |
5 | export const Link: FC<{ href: string; children: ReactNode }> = ({ href, children }) => {
6 | const isExternalUrl = !(href.startsWith('/') || href.startsWith('#'))
7 |
8 | return (
9 |
10 |
15 | {children}
16 | {isExternalUrl && (
17 |
18 |
19 |
20 | )}
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/common/Logo.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 |
3 | export const Logo: FC = () => {
4 | return (
5 |
6 |
14 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/common/PageNavigation.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState, useEffect } from 'react'
2 | import { type DocHeading } from '../../contentlayer/document/Doc'
3 | import { getNodeText, sluggifyTitle } from '../../utils/sluggify'
4 | import { Icon } from './Icon'
5 |
6 | export const PageNavigation: FC<{ headings: DocHeading[] }> = ({ headings }) => {
7 | const [activeHeading, setActiveHeading] = useState('')
8 |
9 | useEffect(() => {
10 | const handleScroll = () => {
11 | let current = ''
12 | for (const heading of headings) {
13 | const slug = sluggifyTitle(getNodeText(heading.title))
14 | const element = document.getElementById(slug)
15 | if (element && element.getBoundingClientRect().top < 240) current = slug
16 | }
17 | setActiveHeading(current)
18 | }
19 | handleScroll()
20 | window.addEventListener('scroll', handleScroll, { passive: true })
21 | return () => {
22 | window.removeEventListener('scroll', handleScroll)
23 | }
24 | }, [headings])
25 |
26 | const headingsToRender = headings.filter((_) => _.level > 1)
27 |
28 | if ((headingsToRender ?? []).length === 0) return null
29 |
30 | return (
31 |
32 |
On this page
33 |
57 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/common/User.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import Image from 'next/image'
3 |
4 | export const User: FC<{ name: string; bio: string; avatar: string }> = ({ name, bio, avatar }) => {
5 | return (
6 |
7 |
12 |
13 |
{name}
14 |
{bio}
15 |
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/docs/DocsCard.tsx:
--------------------------------------------------------------------------------
1 | import { Icon, IconName } from '../common/Icon'
2 | import { Label } from '../common/Label'
3 | import { ChevronLink } from '../common/ChevronLink'
4 |
5 | export const DocsCard: React.FC<
6 | React.PropsWithChildren<{
7 | title: string
8 | icon?: IconName | null
9 | label?: string | null
10 | subtitle?: string | null
11 | link?: { url: string; label: string }
12 | }>
13 | > = ({ title, icon, label, subtitle, children, link }) => {
14 | return (
15 |
16 |
20 | {icon && (
21 |
26 | )}
27 |
{title}
28 | {label &&
}
29 | {subtitle && (
30 |
33 | )}
34 | {children &&
{children}
}
35 |
36 | {link && (
37 |
38 |
39 |
40 | )}
41 |
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/docs/DocsFooter.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import Link from 'next/link'
3 | import { Icon } from '../common/Icon'
4 | import { format } from 'date-fns'
5 | import { Doc } from 'contentlayer/generated'
6 |
7 | const githubBranch = 'main'
8 | const githubBaseUrl = `https://github.com/contentlayerdev/website/blob/${githubBranch}/content/`
9 |
10 | export const DocsFooter: FC<{ doc: Doc }> = ({ doc }) => {
11 | return (
12 | <>
13 |
14 |
38 | >
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/docs/OptionsTable.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react'
2 |
3 | export const OptionsTable: FC<{ children: ReactNode }> = ({ children }) => {
4 | return (
5 |
6 | {children}
7 |
8 | )
9 | }
10 |
11 | export const OptionTitle: FC<{ children: ReactNode }> = ({ children }) => {
12 | return (
13 |
14 | {children}
15 |
16 | )
17 | }
18 |
19 | export const OptionDescription: FC<{ children: ReactNode }> = ({ children }) => {
20 | return (
21 |
22 | {children}
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/src/components/examples/ExamplesFooter.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react'
2 | import Link from 'next/link'
3 | import { Icon } from '../common/Icon'
4 | import { format } from 'date-fns'
5 | import { Doc, Example } from 'contentlayer/generated'
6 |
7 | const githubBranch = 'main'
8 | const githubBaseUrl = `https://github.com/contentlayerdev/website/blob/${githubBranch}/content/`
9 |
10 | export const ExamplesFooter: FC<{ example: Example }> = ({ example }) => {
11 | return (
12 | <>
13 |
14 |
38 | >
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/landing-page/Checklist.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react'
2 | import classnames from 'classnames'
3 | import { Icon } from '../common/Icon'
4 |
5 | export const Checklist: FC<{ items: ReactNode[]; className?: string }> = ({ items, className }) => {
6 | return (
7 |
8 | {items.map((item, index) => (
9 |
10 |
11 |
12 |
13 | {item}
14 |
15 | ))}
16 |
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/landing-page/Dashed.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import * as Tooltip from '@radix-ui/react-tooltip'
3 |
4 | export const Dashed: FC<{ label: string; tooltip: string }> = ({ label, tooltip }) => {
5 | const [showTooltip, setShowTooltip] = useState(false)
6 |
7 | return (
8 | setShowTooltip(open)}>
9 |
10 | setShowTooltip(true)}>{label}
11 |
12 |
16 | {tooltip}
17 |
18 |
19 |
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/landing-page/DataTransformation.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import { FC } from 'react'
3 | import { Arrow } from '../common/Arrow'
4 | import { FileTree } from './FileTree'
5 |
6 | export const DataTransformation: FC<{ from: { type: string; data: any }; to: { type: string; data: any } }> = ({
7 | from,
8 | to,
9 | }) => {
10 | return (
11 |
12 |
16 |
17 | {from.type == 'image' && (
18 |
19 |
27 |
28 | )}
29 | {from.type == 'fileTree' &&
}
30 |
36 | {to.type == 'image' && (
37 |
38 |
46 |
47 | )}
48 | {to.type == 'fileTree' &&
}
49 |
50 |
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/src/components/landing-page/Features.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import { FC } from 'react'
3 | import { Checklist } from './Checklist'
4 | import { Heading } from './Heading'
5 | import { Icon, IconName } from '../common/Icon'
6 | import { Paragraph } from './Paragraph'
7 |
8 | const content = {
9 | blocks: [
10 | {
11 | icon: 'code-light' as IconName,
12 | heading: 'Just use JS/TS',
13 | text: 'No need to learn a new query language or complicated API docs to read. Import and manipulate your content as data directly with the JavaScript methods you know and love.',
14 | features: [
15 | <>
16 | Simply import
your content as data
17 | >,
18 | 'No new query language to learn',
19 | 'Works great with your site framework',
20 | ],
21 | },
22 | {
23 | icon: 'check-circle' as IconName,
24 | heading: 'Built-in code confidence',
25 | text: 'Automatically-generated type definitions and configurable data validations ensure that your data is properly structured across your application.',
26 | features: ['Validates your content & frontmatter', 'Generates TypeScript types', 'Great error messages'],
27 | },
28 | {
29 | icon: 'lightning' as IconName,
30 | heading: 'Build. Faster.',
31 | text: 'Contentlayer + Next.js brings faster build times than Next.js alone or up against other frameworks, like Gatsby.',
32 | features: ['Incremental & parallel builds', 'Instant content live-reload', 'Scales to 100k of documents'],
33 | },
34 | ],
35 | }
36 |
37 | export const Features: FC = () => {
38 | return (
39 |
40 |
41 | {content.blocks.map(({ icon, heading, text, features }, index) => (
42 |
43 |
44 |
45 |
46 |
{heading}
47 |
{text}
48 |
49 |
50 | ))}
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/src/components/landing-page/Heading.tsx:
--------------------------------------------------------------------------------
1 | import { FC, ReactNode } from 'react'
2 | import classNames from 'classnames'
3 |
4 | export const Heading: FC<{ level: number; children: ReactNode; className?: string }> = ({
5 | level,
6 | children,
7 | className,
8 | }) => {
9 | return (
10 | <>
11 | {level == 1 && (
12 |
13 | {children}
14 |
15 | )}
16 | {level == 2 && (
17 |
18 | {children}
19 |
20 | )}
21 | {level == 3 && (
22 |
23 | {children}
24 |
25 | )}
26 | {level == 4 && (
27 |
28 | {children}
29 |
30 | )}
31 | >
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/landing-page/Paragraph.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames'
2 | import { FC, ReactNode } from 'react'
3 |
4 | export const Paragraph: FC<{ children: ReactNode; className?: string }> = ({ children, className }) => {
5 | return {children}
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/landing-page/Video.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useState } from 'react'
2 | import Image from 'next/image'
3 | import { Card } from '../common/Card'
4 | import { Icon } from '../common/Icon'
5 |
6 | export const Video: FC<{
7 | thumbnail: { url: string; alt: string; width?: number; height?: number }
8 | videoId: string
9 | }> = ({ thumbnail, videoId }) => {
10 | const [showVideo, setShowVideo] = useState(false)
11 |
12 | return (
13 |
14 | {showVideo ? (
15 |
16 |
25 |
26 | ) : (
27 |
28 |
36 |
setShowVideo(true)}
39 | >
40 |
41 |
42 |
43 |
44 |
45 | )}
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/contentlayer/document/Example.ts:
--------------------------------------------------------------------------------
1 | import { defineDocumentType } from '@contentlayer/source-files'
2 | import { getLastEditedDate, urlFromFilePath } from '../utils'
3 |
4 | export const Example = defineDocumentType(() => ({
5 | name: 'Example',
6 | filePathPattern: `examples/**/*.mdx`,
7 | contentType: 'mdx',
8 | fields: {
9 | title: {
10 | type: 'string',
11 | description: 'The title of the page',
12 | required: true,
13 | },
14 | nav_title: {
15 | type: 'string',
16 | description: 'Override the title for display in nav',
17 | },
18 | label: {
19 | type: 'string',
20 | },
21 | excerpt: {
22 | type: 'string',
23 | required: true,
24 | },
25 | github_repo: {
26 | type: 'string',
27 | description: 'The string to use in stackblitz.embedGithubProject.',
28 | required: false,
29 | },
30 | open_file: {
31 | type: 'string',
32 | description: 'The file to open in the stackblitz playground.',
33 | required: false,
34 | },
35 | },
36 | computedFields: {
37 | url_path: {
38 | type: 'string',
39 | description:
40 | 'The URL path of this page relative to site root. For example, the site root page would be "/", and doc page would be "docs/getting-started/"',
41 | resolve: urlFromFilePath,
42 | },
43 | pathSegments: {
44 | type: 'json',
45 | resolve: (doc) =>
46 | doc._raw.flattenedPath.split('/').map((dirName) => {
47 | const re = /^((\d+)-)?(.*)$/
48 | const [, , orderStr, pathName] = dirName.match(re) ?? []
49 | const order = orderStr ? parseInt(orderStr) : 0
50 | return { order, pathName }
51 | }),
52 | },
53 | last_edited: { type: 'date', resolve: getLastEditedDate },
54 | },
55 | extensions: {},
56 | }))
57 |
--------------------------------------------------------------------------------
/src/contentlayer/document/GlobalConfig.ts:
--------------------------------------------------------------------------------
1 | import { defineDocumentType } from '@contentlayer/source-files'
2 |
3 | export const GlobalConfig = defineDocumentType(() => ({
4 | name: 'GlobalConfig',
5 | filePathPattern: `config/global.yaml`,
6 | isSingleton: true,
7 | fields: {
8 | title: {
9 | type: 'string',
10 | description: 'The title of the site',
11 | required: true,
12 | },
13 | },
14 | extensions: {},
15 | }))
16 |
--------------------------------------------------------------------------------
/src/contentlayer/index.ts:
--------------------------------------------------------------------------------
1 | export { Doc } from './document/Doc'
2 | export { GlobalConfig } from './document/GlobalConfig'
3 | export { Post } from './document/Post'
4 | export { Example } from './document/Example'
5 |
--------------------------------------------------------------------------------
/src/contentlayer/nested/Link.ts:
--------------------------------------------------------------------------------
1 | import { defineNestedType } from 'contentlayer/source-files'
2 |
3 | export const Link = defineNestedType(() => ({
4 | name: 'Link',
5 | fields: {
6 | label: {
7 | type: 'string',
8 | required: true,
9 | },
10 | url: {
11 | type: 'string',
12 | required: true,
13 | },
14 | isExternal: {
15 | type: 'boolean',
16 | default: false,
17 | },
18 | },
19 | extensions: {},
20 | }))
21 |
--------------------------------------------------------------------------------
/src/contentlayer/nested/SEO.ts:
--------------------------------------------------------------------------------
1 | import { defineNestedType } from 'contentlayer/source-files'
2 |
3 | export const SEO = defineNestedType(() => ({
4 | name: 'SEO',
5 | fields: {
6 | title: {
7 | type: 'string',
8 | },
9 | description: {
10 | type: 'string',
11 | },
12 | imagePath: {
13 | type: 'string',
14 | required: true,
15 | },
16 | },
17 | extensions: {},
18 | }))
19 |
--------------------------------------------------------------------------------
/src/contentlayer/utils.ts:
--------------------------------------------------------------------------------
1 | import type { DocumentGen } from 'contentlayer/core'
2 | import * as fs from 'node:fs/promises'
3 | import path from 'node:path'
4 |
5 | export const contentDirPath = 'content'
6 |
7 | export const urlFromFilePath = (doc: DocumentGen): string => {
8 | let urlPath = doc._raw.flattenedPath.replace(/^pages\/?/, '/')
9 | if (!urlPath.startsWith('/')) urlPath = `/${urlPath}`
10 | if ('global_id' in doc) urlPath += `-${doc.global_id}`
11 | // Remove preceding indexes from path segments
12 | urlPath = urlPath
13 | .split('/')
14 | .map((segment) => segment.replace(/^\d\d\d\-/, ''))
15 | .join('/')
16 | return urlPath
17 | }
18 |
19 | export const getLastEditedDate = async (doc: DocumentGen): Promise => {
20 | const stats = await fs.stat(path.join(contentDirPath, doc._raw.sourceFilePath))
21 | return stats.mtime
22 | }
23 |
--------------------------------------------------------------------------------
/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Container } from '../components/common/Container'
3 | import { Icon } from 'src/components/common/Icon'
4 | import { Heading } from 'src/components/landing-page/Heading'
5 | import { Paragraph } from 'src/components/landing-page/Paragraph'
6 | import { Label } from 'src/components/common/Label'
7 |
8 | const NotFound = () => {
9 | return (
10 |
11 |
12 |
13 |
14 | 404
15 |
16 |
17 |
18 |
19 |
20 | The URL you are looking for does not exist.
21 |
22 | Hit to explore Contentlayer.
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default NotFound
31 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from 'next/app'
2 | import React from 'react'
3 | import { ColorSchemeProvider } from '../components/ColorSchemeContext'
4 |
5 | import '../styles/globals.css'
6 |
7 | function MyApp({ Component, pageProps }: AppProps) {
8 | return (
9 |
10 |
11 |
12 | )
13 | }
14 | export default MyApp
15 |
--------------------------------------------------------------------------------
/src/pages/blog/index.tsx:
--------------------------------------------------------------------------------
1 | import type { InferGetStaticPropsType } from 'next'
2 | // TODO remove eslint-disable when fixed https://github.com/import-js/eslint-plugin-import/issues/1810
3 | // eslint-disable-next-line import/no-unresolved
4 | import { useLiveReload } from 'next-contentlayer/hooks'
5 | import type { FC } from 'react'
6 | import { allDocs, allPosts } from 'contentlayer/generated'
7 | import { Container } from '../../components/common/Container'
8 | import { defineStaticProps } from '../../utils/next'
9 | import { Heading } from '../../components/landing-page/Heading'
10 | import { Paragraph } from '../../components/landing-page/Paragraph'
11 | import { BlogPreview } from 'src/components/blog/BlogPreview'
12 |
13 | const content = {
14 | title: 'Contentlayer Blog',
15 | description: `Working with content for the web shouldn't be difficult. That's why we built Contentlayer.`,
16 | }
17 |
18 | export const getStaticProps = defineStaticProps(async (context) => {
19 | const posts = allPosts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
20 |
21 | return { props: { posts } }
22 | })
23 |
24 | const Blog: FC> = ({ posts }) => {
25 | useLiveReload()
26 |
27 | return (
28 |
29 |
30 |
31 |
{content.title}
32 |
{content.description}
33 |
34 |
35 | {posts.map((post, index) => (
36 |
37 | ))}
38 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default Blog
45 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import url('./tailwind.css');
2 | @import url('./twoslash-shiki.css');
3 | @import url('./hljs-github-dark.css');
4 | @import url('./markdown.css');
5 |
6 | @layer base {
7 | @font-face {
8 | font-family: 'Virgil';
9 | font-style: normal;
10 | font-weight: 400;
11 | font-display: swap;
12 | src: local(''), url('/fonts/virgil.woff2') format('woff2');
13 | }
14 |
15 | .scroll-padding {
16 | scroll-padding-top: 150px;
17 | }
18 |
19 | body {
20 | @apply h-full bg-white text-slate-500 antialiased dark:bg-gray-950 dark:text-slate-400;
21 | }
22 |
23 | svg {
24 | @apply max-w-full;
25 | }
26 |
27 | .svg-h-full svg {
28 | @apply h-full;
29 | }
30 |
31 | svg path {
32 | fill: currentColor;
33 | }
34 |
35 | code {
36 | @apply rounded bg-gray-200 py-0.5 px-1 text-sm dark:bg-gray-800;
37 | }
38 |
39 | .prose code.hljs {
40 | @apply bg-transparent;
41 | }
42 |
43 | .prose a code {
44 | @apply dark:text-violet-400 !important;
45 | }
46 |
47 | .prose pre {
48 | @apply rounded-lg border border-transparent dark:border-gray-900 dark:bg-black;
49 | }
50 |
51 | @media (min-width: 1024px) {
52 | .docs.prose pre {
53 | max-width: calc(100vw - 520px);
54 | }
55 | .blog.prose pre {
56 | max-width: calc(100vw - 352px);
57 | }
58 | }
59 |
60 | @media (min-width: 1280px) {
61 | .blog.prose pre {
62 | max-width: calc(100vw - 672px);
63 | }
64 | }
65 |
66 | @media (min-width: 1440px) {
67 | .docs.prose pre {
68 | max-width: calc(100vw - 840px);
69 | }
70 | }
71 |
72 | @media (min-width: 1536px) {
73 | .docs.prose pre {
74 | max-width: 707px;
75 | }
76 | .blog.prose pre {
77 | max-width: 832px;
78 | }
79 | }
80 |
81 | @keyframes scroll {
82 | 0% {
83 | transform: translateX(0);
84 | }
85 | 100% {
86 | transform: translateX(calc(-560px * 4));
87 | }
88 | }
89 |
90 | .animate-scroll {
91 | animation: scroll 50s linear infinite;
92 | }
93 |
94 | .animate-scroll:hover {
95 | animation-play-state: paused;
96 | }
97 |
98 | reach-portal {
99 | position: absolute;
100 | z-index: 100;
101 | }
102 |
103 | .options-table .option-title h2,
104 | .options-table .option-title h3,
105 | .options-table .option-title h4 {
106 | @apply text-slate-900 dark:text-white;
107 | }
108 |
109 | .options-table .option-title p {
110 | @apply text-sm text-slate-500 dark:text-slate-400;
111 | }
112 |
113 | .hyphens {
114 | hyphens: auto;
115 | -webkit-hyphens: auto;
116 | }
117 |
118 | html.dark {
119 | color-scheme: dark;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/styles/hljs-github-dark.css:
--------------------------------------------------------------------------------
1 | /*!
2 | Theme: GitHub Dark
3 | Description: Dark theme as seen on github.com
4 | Author: github.com
5 | Maintainer: @Hirse
6 | Updated: 2021-05-15
7 |
8 | Outdated base version: https://github.com/primer/github-syntax-dark
9 | Current colors taken from GitHub's CSS
10 | */
11 |
12 | .hljs {
13 | color: #c9d1d9;
14 | background: #0d1117;
15 | }
16 |
17 | .hljs-doctag,
18 | .hljs-keyword,
19 | .hljs-meta .hljs-keyword,
20 | .hljs-template-tag,
21 | .hljs-template-variable,
22 | .hljs-type,
23 | .hljs-variable.language_ {
24 | /* prettylights-syntax-keyword */
25 | color: #ff7b72;
26 | }
27 |
28 | .hljs-title,
29 | .hljs-title.class_,
30 | .hljs-title.class_.inherited__,
31 | .hljs-title.function_ {
32 | /* prettylights-syntax-entity */
33 | color: #d2a8ff;
34 | }
35 |
36 | .hljs-attr,
37 | .hljs-attribute,
38 | .hljs-literal,
39 | .hljs-meta,
40 | .hljs-number,
41 | .hljs-operator,
42 | .hljs-variable,
43 | .hljs-selector-attr,
44 | .hljs-selector-class,
45 | .hljs-selector-id {
46 | /* prettylights-syntax-constant */
47 | color: #79c0ff;
48 | }
49 |
50 | .hljs-regexp,
51 | .hljs-string,
52 | .hljs-meta .hljs-string {
53 | /* prettylights-syntax-string */
54 | color: #a5d6ff;
55 | }
56 |
57 | .hljs-built_in,
58 | .hljs-symbol {
59 | /* prettylights-syntax-variable */
60 | color: #ffa657;
61 | }
62 |
63 | .hljs-comment,
64 | .hljs-code,
65 | .hljs-formula {
66 | /* prettylights-syntax-comment */
67 | color: #8b949e;
68 | }
69 |
70 | .hljs-name,
71 | .hljs-quote,
72 | .hljs-selector-tag,
73 | .hljs-selector-pseudo {
74 | /* prettylights-syntax-entity-tag */
75 | color: #7ee787;
76 | }
77 |
78 | .hljs-subst {
79 | /* prettylights-syntax-storage-modifier-import */
80 | color: #c9d1d9;
81 | }
82 |
83 | .hljs-section {
84 | /* prettylights-syntax-markup-heading */
85 | color: #1f6feb;
86 | font-weight: bold;
87 | }
88 |
89 | .hljs-bullet {
90 | /* prettylights-syntax-markup-list */
91 | color: #f2cc60;
92 | }
93 |
94 | .hljs-emphasis {
95 | /* prettylights-syntax-markup-italic */
96 | color: #c9d1d9;
97 | font-style: italic;
98 | }
99 |
100 | .hljs-strong {
101 | /* prettylights-syntax-markup-bold */
102 | color: #c9d1d9;
103 | font-weight: bold;
104 | }
105 |
106 | .hljs-addition {
107 | /* prettylights-syntax-markup-inserted */
108 | color: #aff5b4;
109 | background-color: #033a16;
110 | }
111 |
112 | .hljs-deletion {
113 | /* prettylights-syntax-markup-deleted */
114 | color: #ffdcd7;
115 | background-color: #67060c;
116 | }
117 |
118 | .hljs-char.escape_,
119 | .hljs-link,
120 | .hljs-params,
121 | .hljs-property,
122 | .hljs-punctuation,
123 | .hljs-tag {
124 | /* purposely ignored */
125 | }
126 |
--------------------------------------------------------------------------------
/src/styles/markdown.css:
--------------------------------------------------------------------------------
1 | @layer components {
2 | .markdown {
3 | @apply text-slate-800 dark:text-slate-100;
4 |
5 | a {
6 | @apply border-b border-purple-500 text-purple-500 no-underline hover:border-purple-700 hover:text-purple-700;
7 | }
8 |
9 | p:last-child {
10 | @apply mb-0;
11 | }
12 | }
13 |
14 | .custom-tldr {
15 | a {
16 | @apply border-b border-dashed border-gray-300 font-medium text-slate-900 no-underline hover:border-solid dark:border-gray-500 dark:text-white;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/utils/build-docs-tree.ts:
--------------------------------------------------------------------------------
1 | import { Doc } from 'contentlayer/generated'
2 | import { TreeNode } from 'types/TreeNode'
3 |
4 | export const buildDocsTree = (docs: Doc[], parentPathNames: string[] = []): TreeNode[] => {
5 | const level = parentPathNames.length
6 |
7 | // Remove ID from parent path
8 | parentPathNames = parentPathNames.join('/').split('-').slice(0, -1).join('-').split('/')
9 |
10 | return docs
11 | .filter(
12 | (_) =>
13 | _.pathSegments.length === level + 1 &&
14 | _.pathSegments
15 | .map((_: PathSegment) => _.pathName)
16 | .join('/')
17 | .startsWith(parentPathNames.join('/')),
18 | )
19 | .sort((a, b) => a.pathSegments[level].order - b.pathSegments[level].order)
20 | .map((doc) => ({
21 | nav_title: doc.nav_title ?? null,
22 | title: doc.title,
23 | label: doc.label ?? null,
24 | excerpt: doc.excerpt ?? null,
25 | urlPath: doc.url_path,
26 | collapsible: doc.collapsible ?? null,
27 | collapsed: doc.collapsed ?? null,
28 | children: buildDocsTree(
29 | docs,
30 | doc.pathSegments.map((_: PathSegment) => _.pathName),
31 | ),
32 | }))
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/build-examples-tree.ts:
--------------------------------------------------------------------------------
1 | import { children } from 'cheerio/lib/api/traversing'
2 | import { Example } from 'contentlayer/generated'
3 | import { TreeNode } from 'types/TreeNode'
4 |
5 | export const buildExamplesTree = (examples: Example[], parentPathNames: string[] = []): TreeNode[] => {
6 | const level = parentPathNames.length
7 |
8 | return examples
9 | .filter(
10 | (_) =>
11 | _.pathSegments.length === level + 1 &&
12 | _.pathSegments
13 | .map((_: PathSegment) => _.pathName)
14 | .join('/')
15 | .startsWith(parentPathNames.join('/')),
16 | )
17 | .sort((a, b) => a.pathSegments[level].order - b.pathSegments[level].order)
18 | .map((example) => ({
19 | nav_title: example.nav_title ?? null,
20 | title: example.title,
21 | label: example.label ?? null,
22 | excerpt: example.excerpt ?? null,
23 | collapsible: false,
24 | collapsed: false,
25 | urlPath: '/' + example.pathSegments.map((_: PathSegment) => _.pathName).join('/'),
26 | children: buildExamplesTree(
27 | examples,
28 | example.pathSegments.map((_: PathSegment) => _.pathName),
29 | ),
30 | }))
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | export const arraysAreEqual = (a: T[], b: T[]) => {
2 | if (a.length !== b.length) {
3 | return false
4 | }
5 |
6 | for (let i = 0; i < a.length; i++) {
7 | if (a[i] !== b[i]) {
8 | return false
9 | }
10 | }
11 |
12 | return true
13 | }
14 |
15 | export const isExternalUrl = (link: string): boolean => !link.startsWith('/')
16 |
--------------------------------------------------------------------------------
/src/utils/next.ts:
--------------------------------------------------------------------------------
1 | import type { GetServerSideProps, GetStaticPaths, GetStaticProps } from 'next'
2 |
3 | /** Needed in combination with `InferGetServerSidePropsType` */
4 | export function defineServerSideProps(fn: Fn): Fn {
5 | return fn
6 | }
7 |
8 | /** Needed in combination with `InferGetStaticPropsType` */
9 | export function defineStaticProps(fn: Fn): Fn {
10 | return fn
11 | }
12 |
13 | export function defineStaticPaths(fn: Fn): Fn {
14 | return fn
15 | }
16 |
17 | export function toParams(path: string): { params: { slug: string[] } } {
18 | return { params: { slug: path.replace(/^\//, '').split('/') } }
19 | }
20 |
21 | export function notUndefined(_: T | undefined): _ is T {
22 | return _ !== undefined
23 | }
24 |
--------------------------------------------------------------------------------
/src/utils/object.ts:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/marcelowa/promise-all-properties/blob/main/src/promiseAllProperties.ts
2 | type PlainObj = Record
3 | export type PromisesMap = {
4 | [P in keyof T]: Promise | T[P]
5 | }
6 |
7 | /**
8 | * Receives an object with promise containing properties and returns a promise that resolves to an object
9 | * with the same properties containing the resolved values
10 | * @param {PromisesMap} promisesMap the input object with a promise in each property
11 | * @return {Promise} a promise that resolved to an object with the same properties containing the resolved values
12 | */
13 | export const promiseAllProperties = (promisesMap: PromisesMap): Promise => {
14 | if (
15 | !(typeof process !== undefined && process.env.NODE_ENV === 'production') &&
16 | (promisesMap === null || typeof promisesMap !== 'object' || Array.isArray(promisesMap))
17 | ) {
18 | return Promise.reject(new TypeError('The input argument must be a plain object'))
19 | }
20 |
21 | const keys = Object.keys(promisesMap)
22 | const promises = keys.map((key) => {
23 | return (promisesMap as any)[key]
24 | })
25 |
26 | return Promise.all(promises).then((results) => {
27 | return results.reduce((resolved, result, index) => {
28 | resolved[keys[index]] = result
29 | return resolved
30 | }, {})
31 | })
32 | }
33 |
34 | type ValueOfRecord> = R extends Record ? V : never
35 |
36 | export const mapObjectValues = , V_Out>(
37 | obj: O_In,
38 | mapValue: (key: keyof O_In, val: ValueOfRecord) => V_Out,
39 | ): { [K in keyof O_In]: V_Out } => {
40 | const mappedEntries = Object.entries(obj).map(([key, val]) => [key, mapValue(key as keyof O_In, val)] as const)
41 | return Object.fromEntries(mappedEntries) as any
42 | }
43 |
--------------------------------------------------------------------------------
/src/utils/sluggify.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export const sluggifyTitle = (title: string) => {
4 | const re = /[^\w\s]/g
5 |
6 | return title.trim().toLowerCase().replace(re, '').replace(/\s+/g, '-')
7 | }
8 |
9 | export const getNodeText = (node: React.ReactNode): string => {
10 | if (typeof node === 'string') return node
11 | if (typeof node === 'number') return node.toString()
12 | if (node instanceof Array) return node.map(getNodeText).join('')
13 |
14 | if (typeof node === 'object' && (node as any)?.props?.children) return getNodeText((node as any).props.children)
15 |
16 | // console.log(node)
17 | // console.error(`Should be never reached`)
18 | // debugger
19 |
20 | return ''
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/syntax-highlighting.ts:
--------------------------------------------------------------------------------
1 | import { renderCodeToHTML, runTwoSlash, createShikiHighlighter, type UserConfigSettings } from 'shiki-twoslash'
2 | import { getHighlighter } from 'shiki'
3 | type Highlighter = Awaited>
4 |
5 | const highlighterMap = new Map()
6 |
7 | export type ColorScheme = 'light' | 'dark'
8 |
9 | export const snippetToHtml = async (snippet: string, colorScheme: ColorScheme) => {
10 | const themeName = `github-${colorScheme}`
11 |
12 | if (!highlighterMap.has(colorScheme)) {
13 | highlighterMap.set(colorScheme, await getHighlighter({ theme: themeName }))
14 | }
15 |
16 | const settings: UserConfigSettings = {
17 | includeJSDocInHover: true,
18 | defaultCompilerOptions: {
19 | strict: false,
20 | noImplicitAny: false,
21 | },
22 | }
23 |
24 | const twoslash = runTwoSlash(snippet, 'tsx', settings)
25 | const html = renderCodeToHTML(
26 | twoslash.code,
27 | 'tsx',
28 | { twoslash: true },
29 | { ...settings, themeName },
30 | highlighterMap.get(colorScheme)!,
31 | twoslash,
32 | )
33 |
34 | return html.replace('./assets/contentlayer-generated', 'contentlayer/generated')
35 | }
36 |
--------------------------------------------------------------------------------
/src/utils/validate-duplicate-ids.ts:
--------------------------------------------------------------------------------
1 | import { Doc } from 'contentlayer/generated'
2 |
3 | export async function validateDuplicateIds(allDocs: Doc[]) {
4 | const ids = allDocs.map((doc) => doc.global_id)
5 |
6 | const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index)
7 | if (duplicates.length) {
8 | throw new Error(`[Error] Duplicate ids found: ${duplicates.join(', ')}`)
9 | }
10 |
11 | console.log('No duplicate ids found')
12 | }
13 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ['./src/**/*.{js,ts,jsx,tsx}', './content/**/*.{md,mdx}'],
3 | darkMode: 'class',
4 | theme: {
5 | extend: {
6 | fontFamily: {
7 | handwritten: 'Virgil',
8 | },
9 | colors: {
10 | gray: {
11 | 850: '#18202F',
12 | 950: '#0b0f1a',
13 | },
14 | },
15 | screens: {
16 | '1.5xl': '1440px',
17 | },
18 | },
19 | },
20 | plugins: [require('tailwindcss-radix')(), require('@tailwindcss/typography')],
21 | }
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "noErrorTruncation": true,
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve",
17 | "incremental": true,
18 | "baseUrl": ".",
19 | "paths": {
20 | "contentlayer/generated": ["./.contentlayer/generated"]
21 | }
22 | },
23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".contentlayer/generated"],
24 | "exclude": ["node_modules"]
25 | }
26 |
--------------------------------------------------------------------------------
/types/PathSegment.d.ts:
--------------------------------------------------------------------------------
1 | type PathSegment = { order: number; pathName: string }
2 |
--------------------------------------------------------------------------------
/types/TreeNode.d.ts:
--------------------------------------------------------------------------------
1 | export type TreeNode = {
2 | title: string
3 | nav_title: string | null
4 | label: string | null
5 | excerpt: string | null
6 | urlPath: string
7 | children: TreeNode[]
8 | collapsible: boolean | null
9 | collapsed: boolean | null
10 | }
11 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "silent": true
4 | },
5 | "headers": [
6 | {
7 | "source": "/fonts/virgil.woff2",
8 | "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------