diff --git a/examples/cms-agilitycms/README.md b/examples/cms-agilitycms/README.md
index 39d2ae1c1d7c4..77614b2a2f7a7 100644
--- a/examples/cms-agilitycms/README.md
+++ b/examples/cms-agilitycms/README.md
@@ -33,6 +33,7 @@ Once you have access to [the environment variables you'll need](#step-15-set-up-
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-builder-io/README.md b/examples/cms-builder-io/README.md
index c047a3d3b4b73..a3995f40c66fc 100644
--- a/examples/cms-builder-io/README.md
+++ b/examples/cms-builder-io/README.md
@@ -97,4 +97,5 @@ Alternatively, you can deploy using our template by clicking on the Deploy butto
- [Kontent](/examples/cms-kontent)
- [Ghost](/examples/cms-ghost)
- [Blog Starter](/examples/blog-starter)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
diff --git a/examples/cms-buttercms/README.md b/examples/cms-buttercms/README.md
index 53ff1b125ad0c..19d5554eedee6 100644
--- a/examples/cms-buttercms/README.md
+++ b/examples/cms-buttercms/README.md
@@ -34,6 +34,7 @@ Once you have access to your Butter API token, you can deploy your Butterized pr
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-contentful/README.md b/examples/cms-contentful/README.md
index dce91203d85e0..3002e636dba56 100644
--- a/examples/cms-contentful/README.md
+++ b/examples/cms-contentful/README.md
@@ -30,6 +30,7 @@ Using the Deploy Button below, you'll deploy the Next.js project as well as conn
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-cosmic/README.md b/examples/cms-cosmic/README.md
index fbce6cd625710..8f5bd84111c5d 100644
--- a/examples/cms-cosmic/README.md
+++ b/examples/cms-cosmic/README.md
@@ -30,6 +30,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-datocms/README.md b/examples/cms-datocms/README.md
index 5ac92863d3096..b5eb1a852af68 100644
--- a/examples/cms-datocms/README.md
+++ b/examples/cms-datocms/README.md
@@ -24,6 +24,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## Deploy your own
diff --git a/examples/cms-dotcms/.env.local.example b/examples/cms-dotcms/.env.local.example
new file mode 100644
index 0000000000000..de332a396448a
--- /dev/null
+++ b/examples/cms-dotcms/.env.local.example
@@ -0,0 +1,3 @@
+NEXT_PUBLIC_DOTCMS_HOST=""
+NEXT_PREVIEW_SECRET_TOKEN=""
+DOTCMS_PREVIEW_SECRET=""
diff --git a/examples/cms-dotcms/.eslintrc.json b/examples/cms-dotcms/.eslintrc.json
new file mode 100644
index 0000000000000..a2569c2c7ca0a
--- /dev/null
+++ b/examples/cms-dotcms/.eslintrc.json
@@ -0,0 +1,4 @@
+{
+ "root": true,
+ "extends": "next/core-web-vitals"
+}
diff --git a/examples/cms-dotcms/.gitignore b/examples/cms-dotcms/.gitignore
new file mode 100644
index 0000000000000..5eaa9b9933213
--- /dev/null
+++ b/examples/cms-dotcms/.gitignore
@@ -0,0 +1,38 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# vercel
+.vercel
+/.env
+
+# typescript
+*.tsbuildinfo
diff --git a/examples/cms-dotcms/README.md b/examples/cms-dotcms/README.md
new file mode 100644
index 0000000000000..2ea73d441a83c
--- /dev/null
+++ b/examples/cms-dotcms/README.md
@@ -0,0 +1,56 @@
+# A statically generated blog example using Next.js and dotCMS
+
+This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [dotCMS](https://dotcms.com/) as the data source.
+
+## Demo
+
+### [https://nextjs-dotcms-blog.vercel.app/](https://nextjs-dotcms-blog.vercel.app/)
+
+## Deploy your own
+
+Using the Deploy Button below, you'll deploy the Next.js project.
+
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2FdotCMS%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fcms-dotcms&project-name=nextjs-dotcms-blog&repository-name=nextjs-dotcms-blog&demo-title=Next.js+Blog&demo-description=Static+blog+with+multiple+authors+using+Preview+Mode&demo-url=https%3A%2F%2Fnext-blog-dotcms.vercel.app%2F)
+
+### Related examples
+
+- [WordPress](/examples/cms-wordpress)
+- [DatoCMS](/examples/cms-datocms)
+- [Sanity](/examples/cms-sanity)
+- [TakeShape](/examples/cms-takeshape)
+- [Prismic](/examples/cms-prismic)
+- [Strapi](/examples/cms-strapi)
+- [Agility CMS](/examples/cms-agilitycms)
+- [Cosmic](/examples/cms-cosmic)
+- [ButterCMS](/examples/cms-buttercms)
+- [Storyblok](/examples/cms-storyblok)
+- [GraphCMS](/examples/cms-graphcms)
+- [Kontent](/examples/cms-kontent)
+- [Ghost](/examples/cms-ghost)
+- [Contentful](/examples/cms-contentful)
+- [Blog Starter](/examples/blog-starter)
+
+## How to use
+
+Rename `.env.local.example` to `.env.local` and complete the variables:
+
+`NEXT_PUBLIC_DOTCMS_HOST` is the dotCMS host, you can use `https://demo.dotcms.com`
+`DOTCMS_TOKEN` for the demo site, you can generate the token using:
+
+```
+curl -H "Content-Type:application/json" --insecure -X POST -d '
+{ "user":"admin@dotcms.com", "password":"admin", "expirationDays": 10 }
+' http://demo.dotcms.com:8080/api/v1/authentication/api-token
+```
+
+Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
+
+```bash
+npx create-next-app --example cms-dotcms cms-dotcms-app
+# or
+yarn create next-app --example cms-dotcms cms-dotcms-app
+# or
+pnpm create next-app --example cms-dotcms cms-dotcms-app
+```
+
+[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fcms-dotcms&project-name=nextjs-dotcms-blog&repository-name=nextjs-dotcms-blog&demo-title=Next.js+Blog&demo-description=Static+blog+with+multiple+authors+using+Preview+Mode&demo-url=https%3A%2F%2Fnext-blog-dotcms.vercel.app%2F)
diff --git a/examples/cms-dotcms/components/alert.tsx b/examples/cms-dotcms/components/alert.tsx
new file mode 100644
index 0000000000000..bb6838dfdc8fa
--- /dev/null
+++ b/examples/cms-dotcms/components/alert.tsx
@@ -0,0 +1,42 @@
+import Container from './container'
+import cn from 'classnames'
+import { EXAMPLE_PATH } from '@lib/constants'
+
+export default function Alert({ preview }) {
+ return (
+
+
+
+ {preview ? (
+ <>
+ This is page is a preview.{' '}
+
+ Click here
+ {' '}
+ to exit preview mode.
+ >
+ ) : (
+ <>
+ The source code for this blog is{' '}
+
+ available on GitHub
+
+ .
+ >
+ )}
+
+
+
+ )
+}
diff --git a/examples/cms-dotcms/components/avatar.tsx b/examples/cms-dotcms/components/avatar.tsx
new file mode 100644
index 0000000000000..b683a70db2ab7
--- /dev/null
+++ b/examples/cms-dotcms/components/avatar.tsx
@@ -0,0 +1,19 @@
+import DotCmsImage from './dotcms-image'
+
+export default function Avatar({ name, picture }) {
+ return (
+
+
+ {picture?.idPath ? (
+
+ ) : null}
+
+
{name}
+
+ )
+}
diff --git a/examples/cms-dotcms/components/blocks.tsx b/examples/cms-dotcms/components/blocks.tsx
new file mode 100644
index 0000000000000..4aaa5ce68353f
--- /dev/null
+++ b/examples/cms-dotcms/components/blocks.tsx
@@ -0,0 +1,98 @@
+import cn from 'classnames'
+import DotCmsImage from './dotcms-image'
+import Link from 'next/link'
+
+export const Bold = ({ children }) => {children}
+export const Italic = ({ children }) => {children}
+export const Strike = ({ children }) => {children}
+export const Underline = ({ children }) => {children}
+export const DotLink = ({ attrs: { href, target }, children }) => {
+ const regEx = /https?:\/\//
+
+ return regEx.test(href) ? (
+
+ {children}
+
+ ) : (
+
+ {children}
+
+ )
+}
+
+const nodeMarks = {
+ link: DotLink,
+ bold: Bold,
+ underline: Underline,
+ italic: Italic,
+ strike: Strike,
+}
+
+export const TextNode = (props) => {
+ const { marks = [], text } = props
+ const mark = marks[0] || { type: '', attrs: {} }
+ const newProps = { ...props, marks: marks.slice(1) }
+ const Component = nodeMarks[mark?.type]
+
+ if (!Component) {
+ return text
+ }
+
+ return (
+
+
+
+ )
+}
+
+export const DotImage = ({ attrs: { textAlign, data } }) => {
+ const { asset, title } = data
+ const [imgTitle] = title.split('.')
+
+ return (
+
+ )
+}
+
+export const ListItem = ({ children }) => {
+ return {children}
+}
+
+export const OrderedList = ({ children }) => {
+ return {children}
+}
+
+export const Paragraph = ({ children }) => {
+ return {children}
+}
+
+export const BulletList = ({ children }) => {
+ return
+}
+
+export const Heading = ({ level, children }) => {
+ const Tag = `h${level}` as keyof JSX.IntrinsicElements
+ return {children}
+}
+
+export const BlockQuote = ({ children }) => {
+ return {children}
+}
+
+export const CodeBlock = ({ language, children }) => {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/examples/cms-dotcms/components/container.tsx b/examples/cms-dotcms/components/container.tsx
new file mode 100644
index 0000000000000..fc1c29dfb0747
--- /dev/null
+++ b/examples/cms-dotcms/components/container.tsx
@@ -0,0 +1,3 @@
+export default function Container({ children }) {
+ return {children}
+}
diff --git a/examples/cms-dotcms/components/content-blocks.tsx b/examples/cms-dotcms/components/content-blocks.tsx
new file mode 100644
index 0000000000000..dea23142b779e
--- /dev/null
+++ b/examples/cms-dotcms/components/content-blocks.tsx
@@ -0,0 +1,90 @@
+import {
+ BlockQuote,
+ BulletList,
+ CodeBlock,
+ DotImage,
+ Heading,
+ ListItem,
+ OrderedList,
+ Paragraph,
+ TextNode,
+} from './blocks'
+
+/*
+ dotCMS Block Editor is a new rich content editor that allows you to create your content as building blocks.
+
+ More info: https://dotcms.com/docs/latest/block-editor
+*/
+
+export const ContentBlocks = ({ content }) => {
+ return (
+ <>
+ {content?.map((data, index) => {
+ switch (data.type) {
+ case 'paragraph':
+ return (
+
+
+
+ )
+ case 'heading':
+ return (
+
+
+
+ )
+
+ case 'bulletList':
+ return (
+
+
+
+ )
+
+ case 'orderedList':
+ return (
+
+
+
+ )
+
+ case 'dotImage':
+ return
+
+ case 'horizontalRule':
+ return
+
+ case 'blockquote':
+ return (
+
+
+
+ )
+
+ case 'codeBlock':
+ return (
+
+
+
+ )
+
+ case 'hardBreak':
+ return
+
+ case 'text':
+ return
+
+ case 'listItem':
+ return (
+
+
+
+ )
+
+ default:
+ return Block not supported
+ }
+ })}
+ >
+ )
+}
diff --git a/examples/cms-dotcms/components/cover-image.tsx b/examples/cms-dotcms/components/cover-image.tsx
new file mode 100644
index 0000000000000..392f01b52e797
--- /dev/null
+++ b/examples/cms-dotcms/components/cover-image.tsx
@@ -0,0 +1,27 @@
+import DotCmsImage from './dotcms-image'
+import Link from 'next/link'
+import cn from 'classnames'
+
+export default function CoverImage(props) {
+ const image = (
+
+ )
+
+ return (
+
+ {props.slug ? (
+
+
{image}
+
+ ) : (
+ image
+ )}
+
+ )
+}
diff --git a/examples/cms-dotcms/components/date.tsx b/examples/cms-dotcms/components/date.tsx
new file mode 100644
index 0000000000000..3a02dc6800b63
--- /dev/null
+++ b/examples/cms-dotcms/components/date.tsx
@@ -0,0 +1,9 @@
+import { format } from 'date-fns'
+
+export default function DateComponent({ dateString }) {
+ return (
+
+ {format(new Date(dateString), 'LLLL d, yyyy')}
+
+ )
+}
diff --git a/examples/cms-dotcms/components/dotcms-image.tsx b/examples/cms-dotcms/components/dotcms-image.tsx
new file mode 100644
index 0000000000000..4b43669af28a4
--- /dev/null
+++ b/examples/cms-dotcms/components/dotcms-image.tsx
@@ -0,0 +1,31 @@
+import Image from 'next/image'
+const DEFAULT_QUALITY = 20
+
+// https://dotcms.com/docs/latest/image-resizing-and-processing
+const getUrlWithResizingParameters = ({
+ src,
+ width,
+ quality = DEFAULT_QUALITY,
+}) => {
+ const urlParams = []
+ const lastSeparatorIdx = src.lastIndexOf('/')
+ const imageIdentifierAndField = src.slice(0, lastSeparatorIdx)
+
+ urlParams.push(imageIdentifierAndField)
+ urlParams.push(width + 'w')
+ urlParams.push(quality + 'q')
+
+ return urlParams.join('/')
+}
+
+const dotCmsLoader = (props) => {
+ return `${process.env.NEXT_PUBLIC_DOTCMS_HOST}${getUrlWithResizingParameters(
+ props
+ )}`
+}
+
+const DotCmsImage = (params) => {
+ return
+}
+
+export default DotCmsImage
diff --git a/examples/cms-dotcms/components/footer.tsx b/examples/cms-dotcms/components/footer.tsx
new file mode 100644
index 0000000000000..b36b942c4a59c
--- /dev/null
+++ b/examples/cms-dotcms/components/footer.tsx
@@ -0,0 +1,30 @@
+import Container from './container'
+import { EXAMPLE_PATH } from '@lib/constants'
+
+export default function Footer() {
+ return (
+
+
+
+
+ Statically Generated with Next.js.
+
+
+
+
+
+ )
+}
diff --git a/examples/cms-dotcms/components/header.tsx b/examples/cms-dotcms/components/header.tsx
new file mode 100644
index 0000000000000..562e7e3eebb6a
--- /dev/null
+++ b/examples/cms-dotcms/components/header.tsx
@@ -0,0 +1,12 @@
+import Link from 'next/link'
+
+export default function Header() {
+ return (
+
+
+ Blog
+
+ .
+
+ )
+}
diff --git a/examples/cms-dotcms/components/hero-post.tsx b/examples/cms-dotcms/components/hero-post.tsx
new file mode 100644
index 0000000000000..cfdc02c4b6e9e
--- /dev/null
+++ b/examples/cms-dotcms/components/hero-post.tsx
@@ -0,0 +1,55 @@
+import Link from 'next/link'
+import Avatar from '@components/avatar'
+import DateComponent from '@components/date'
+import CoverImage from '@components/cover-image'
+import cn from 'classnames'
+
+export default function HeroPost({
+ title,
+ coverImage,
+ date,
+ excerpt,
+ author,
+ slug,
+}) {
+ return (
+
+
+
+
+
+
+
+
{excerpt}
+ {author.length ? (
+
+ ) : null}
+
+
+
+ )
+}
diff --git a/examples/cms-dotcms/components/intro.tsx b/examples/cms-dotcms/components/intro.tsx
new file mode 100644
index 0000000000000..cfa4cad95f13c
--- /dev/null
+++ b/examples/cms-dotcms/components/intro.tsx
@@ -0,0 +1,28 @@
+import { CMS_NAME, CMS_URL } from '@lib/constants'
+
+export default function Intro() {
+ return (
+
+ )
+}
diff --git a/examples/cms-dotcms/components/layout.tsx b/examples/cms-dotcms/components/layout.tsx
new file mode 100644
index 0000000000000..15fb7e1f3db21
--- /dev/null
+++ b/examples/cms-dotcms/components/layout.tsx
@@ -0,0 +1,16 @@
+import Alert from '@components/alert'
+import Footer from '@components/footer'
+import Meta from '@components/meta'
+
+export default function Layout({ preview, children }) {
+ return (
+ <>
+
+
+
+ >
+ )
+}
diff --git a/examples/cms-dotcms/components/meta.tsx b/examples/cms-dotcms/components/meta.tsx
new file mode 100644
index 0000000000000..28c8cb4dc3c51
--- /dev/null
+++ b/examples/cms-dotcms/components/meta.tsx
@@ -0,0 +1,42 @@
+import Head from 'next/head'
+import { CMS_NAME, HOME_OG_IMAGE_URL } from '@lib/constants'
+
+export default function Meta() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/examples/cms-dotcms/components/more-stories.tsx b/examples/cms-dotcms/components/more-stories.tsx
new file mode 100644
index 0000000000000..35c2017c8e71c
--- /dev/null
+++ b/examples/cms-dotcms/components/more-stories.tsx
@@ -0,0 +1,24 @@
+import PostPreview from '@components/post-preview'
+
+export default function MoreStories({ posts }) {
+ return (
+
+
+ More Stories
+
+
+ {posts.map((post) => (
+
+ ))}
+
+
+ )
+}
diff --git a/examples/cms-dotcms/components/post-body.tsx b/examples/cms-dotcms/components/post-body.tsx
new file mode 100644
index 0000000000000..539c95152d210
--- /dev/null
+++ b/examples/cms-dotcms/components/post-body.tsx
@@ -0,0 +1,26 @@
+import { ContentBlocks } from './content-blocks'
+import DateComponent from './date'
+import Avatar from './avatar'
+
+export default function PostBody({ content }) {
+ return (
+
+
+ {content.author.length ? (
+
+ ) : null}
+
+
+ {content.postingDate !== 'now' ? (
+
+ Posted
+
+ ) : null}
+
+
+
+ )
+}
diff --git a/examples/cms-dotcms/components/post-header.tsx b/examples/cms-dotcms/components/post-header.tsx
new file mode 100644
index 0000000000000..a38be556436e0
--- /dev/null
+++ b/examples/cms-dotcms/components/post-header.tsx
@@ -0,0 +1,29 @@
+import Avatar from '@components/avatar'
+import CoverImage from '@components/cover-image'
+import PostTitle from '@components/post-title'
+
+export default function PostHeader({ title, coverImage, author }) {
+ return (
+ <>
+ {title}
+
+ {author.length ? (
+
+ ) : null}
+
+
+
+
+ >
+ )
+}
diff --git a/examples/cms-dotcms/components/post-preview.tsx b/examples/cms-dotcms/components/post-preview.tsx
new file mode 100644
index 0000000000000..ac953c300ae66
--- /dev/null
+++ b/examples/cms-dotcms/components/post-preview.tsx
@@ -0,0 +1,46 @@
+import Link from 'next/link'
+import Avatar from '@components/avatar'
+import DateComponent from '@components/date'
+import CoverImage from './cover-image'
+
+export default function PostPreview({
+ title,
+ coverImage,
+ date,
+ excerpt,
+ author,
+ slug,
+}) {
+ return (
+
+
+
+
+
+ {date !== 'now' ? (
+
+
+
+ ) : null}
+
{excerpt}
+ {author.length ? (
+
+ ) : null}
+
+ )
+}
diff --git a/examples/cms-dotcms/components/post-title.tsx b/examples/cms-dotcms/components/post-title.tsx
new file mode 100644
index 0000000000000..edd8cba65c257
--- /dev/null
+++ b/examples/cms-dotcms/components/post-title.tsx
@@ -0,0 +1,7 @@
+export default function PostTitle({ children }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/examples/cms-dotcms/components/section-separator.tsx b/examples/cms-dotcms/components/section-separator.tsx
new file mode 100644
index 0000000000000..4ca5c65fdc6ee
--- /dev/null
+++ b/examples/cms-dotcms/components/section-separator.tsx
@@ -0,0 +1,3 @@
+export default function SectionSeparator() {
+ return
+}
diff --git a/examples/cms-dotcms/lib/api.ts b/examples/cms-dotcms/lib/api.ts
new file mode 100644
index 0000000000000..d44ba03bb900f
--- /dev/null
+++ b/examples/cms-dotcms/lib/api.ts
@@ -0,0 +1,176 @@
+/**
+ * A helper for the GraphQL API.
+ *
+ * @param {String} query - The query to fetch for
+ * @param {Object} param1.variables - The variables to pass to the query
+ * @param {Object} param1.preview - Indicate if the query should be previewed
+ * @returns {Promise} - A promise that resolves to the result of the query
+ */
+async function fetchAPI(query, { variables } = { variables: null }) {
+ const res = await fetch(
+ process.env.NEXT_PUBLIC_DOTCMS_HOST + '/api/v1/graphql',
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${process.env.DOTCMS_TOKEN}`,
+ },
+ body: JSON.stringify({
+ query,
+ variables,
+ }),
+ }
+ )
+
+ const json = await res.json()
+
+ if (json.errors) {
+ console.error(json.errors)
+ throw new Error('Failed to fetch API')
+ }
+
+ return json.data
+}
+
+/**
+ * Get the correct type to filter post using preview flag
+ *
+ * @param preview
+ * @returns {string}
+ */
+const showPreviewPosts = (preview) => {
+ return preview === true
+ ? '+working:true +deleted:false'
+ : '+live:true +deleted:false'
+}
+
+/**
+ * Fetch a single post and more posts
+ *
+ * @param {String} slug - The slug of the post to fetch
+ * @param {boolean} preview - Whether or not to fetch the live post
+ * @returns An object with a post and more posts array
+ */
+export async function getPostAndMorePosts(slug, preview) {
+ const data = await fetchAPI(
+ `
+ query PostBySlug($query: String!, $morePostsQuery: String!) {
+ post: BlogCollection(query: $query, limit: 1) {
+ title
+ urlTitle
+ blogContent {
+ json
+ }
+ postingDate
+ image {
+ idPath
+ }
+ author {
+ firstName
+ lastName
+ profilePhoto {
+ idPath
+ }
+ }
+ }
+
+ morePosts: BlogCollection(query: $morePostsQuery, limit: 2) {
+ title
+ urlTitle
+ teaser
+ postingDate
+ image {
+ idPath
+ }
+ author {
+ firstName
+ lastName
+ profilePhoto {
+ idPath
+ }
+ }
+ }
+ }
+ `,
+ {
+ variables: {
+ query: `+urlmap:/blog/post/${slug} ${showPreviewPosts(preview)}`,
+ morePostsQuery: `-urlmap:/blog/post/${slug} ${showPreviewPosts(
+ preview
+ )}`,
+ },
+ }
+ )
+ return {
+ post: data?.post[0] ?? {},
+ morePosts: data?.morePosts ?? [],
+ }
+}
+
+/**
+ * Fetch one post and more post with preview mode flag.
+ *
+ * @param slug
+ * @param isPreview
+ * @returns {Promise<{post, morePosts}>}
+ */
+export async function getPreviewPostBySlug(slug, isPreview) {
+ return await getPostAndMorePosts(slug, isPreview)
+}
+
+/**
+ * Fetch all posts with slug
+ *
+ * @returns An array of posts with the following shape:
+ * {
+ * urlTitle: string
+ * }
+ */
+export async function getAllPostsWithSlug() {
+ const entries = await fetchAPI(`
+ query getAllPostsWithSlug {
+ BlogCollection(query: "+live:true +deleted:false") {
+ urlTitle
+ }
+ }
+ `)
+
+ return entries?.BlogCollection ?? []
+}
+
+/**
+ * Fetch all posts
+ *
+ * @param {boolean} preview - If true, return a preview of the post
+ * @returns An array of posts
+ */
+export async function getAllPostsForHome(preview) {
+ const entries = await fetchAPI(
+ `
+ query getAllPostsForHome($query: String!) {
+ BlogCollection(query: $query) {
+ title
+ teaser
+ postingDate
+ author {
+ firstName
+ lastName
+ profilePhoto {
+ idPath
+ }
+ }
+ urlTitle
+ image {
+ idPath
+ }
+ }
+ }
+ `,
+ {
+ variables: {
+ query: `${showPreviewPosts(preview)}`,
+ },
+ }
+ )
+ return entries?.BlogCollection ?? []
+}
diff --git a/examples/cms-dotcms/lib/constants.ts b/examples/cms-dotcms/lib/constants.ts
new file mode 100644
index 0000000000000..2b126e19611f3
--- /dev/null
+++ b/examples/cms-dotcms/lib/constants.ts
@@ -0,0 +1,5 @@
+export const EXAMPLE_PATH = 'cms-dotcms'
+export const CMS_NAME = 'dotCMS'
+export const CMS_URL = 'https://dotcms.com/'
+export const HOME_OG_IMAGE_URL =
+ 'https://og-image.vercel.app/Next.js%20Blog%20Example%20with%20**dotCMS**.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg&images=https%3A%2F%2Fcdn.dotcms.com%2FdA%2F99fe3769-d649%2F256w%2Fdotcms.png&widths=undefined&widths=350&heights=undefined&heights=auto'
diff --git a/examples/cms-dotcms/next-env.d.ts b/examples/cms-dotcms/next-env.d.ts
new file mode 100644
index 0000000000000..4f11a03dc6cc3
--- /dev/null
+++ b/examples/cms-dotcms/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/examples/cms-dotcms/next.config.js b/examples/cms-dotcms/next.config.js
new file mode 100644
index 0000000000000..6313c9e36d2b2
--- /dev/null
+++ b/examples/cms-dotcms/next.config.js
@@ -0,0 +1,16 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ async rewrites() {
+ const baseUrl = process.env.NEXT_PUBLIC_DOTCMS_HOST
+ return [
+ {
+ source: '/images/:slug*',
+ destination: `${baseUrl}/images/:slug*`,
+ },
+ ]
+ },
+ reactStrictMode: true,
+ swcMinify: true,
+}
+
+module.exports = nextConfig
diff --git a/examples/cms-dotcms/package.json b/examples/cms-dotcms/package.json
new file mode 100644
index 0000000000000..3ab9ee0be713c
--- /dev/null
+++ b/examples/cms-dotcms/package.json
@@ -0,0 +1,28 @@
+{
+ "private": true,
+ "scripts": {
+ "dev": "next",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@tailwindcss/typography": "^0.5.7",
+ "classnames": "^2.3.2",
+ "date-fns": "^2.29.3",
+ "next": "latest",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "tailwindcss": "^3.1.8"
+ },
+ "devDependencies": {
+ "@types/node": "^18.7.18",
+ "@types/react": "^18.0.21",
+ "@types/react-dom": "^18.0.6",
+ "autoprefixer": "^10.4.12",
+ "eslint": "^8.23.1",
+ "eslint-config-next": "latest",
+ "postcss": "^8.4.16",
+ "typescript": "^4.8.3"
+ }
+}
diff --git a/examples/cms-dotcms/pages/_app.tsx b/examples/cms-dotcms/pages/_app.tsx
new file mode 100644
index 0000000000000..e59c9508cdab8
--- /dev/null
+++ b/examples/cms-dotcms/pages/_app.tsx
@@ -0,0 +1,7 @@
+import '@styles/index.css'
+
+function MyApp({ Component, pageProps }) {
+ return
+}
+
+export default MyApp
diff --git a/examples/cms-dotcms/pages/api/exit-preview.tsx b/examples/cms-dotcms/pages/api/exit-preview.tsx
new file mode 100644
index 0000000000000..6c63a0a6e8a42
--- /dev/null
+++ b/examples/cms-dotcms/pages/api/exit-preview.tsx
@@ -0,0 +1,8 @@
+export default async function exit(_, res) {
+ // Exit the current user from "Preview Mode". This function accepts no args.
+ res.clearPreviewData()
+
+ // Redirect the user back to the index page.
+ res.writeHead(307, { Location: '/' })
+ res.end()
+}
diff --git a/examples/cms-dotcms/pages/api/preview.tsx b/examples/cms-dotcms/pages/api/preview.tsx
new file mode 100644
index 0000000000000..45a62aa465094
--- /dev/null
+++ b/examples/cms-dotcms/pages/api/preview.tsx
@@ -0,0 +1,31 @@
+import { getPreviewPostBySlug } from '@lib/api'
+
+export default async function preview(req, res) {
+ const { secret, slug } = req.query
+
+ if (secret !== process.env.DOTCMS_PREVIEW_SECRET || !slug) {
+ return res.status(401).json({ message: 'Invalid token' })
+ }
+
+ // Fetch the headless CMS to check if the provided `slug` exists
+ const post = await getPreviewPostBySlug(slug, true)
+
+ // If the slug doesn't exist prevent preview mode from being enabled
+ if (Object.keys(post.post).length < 1) {
+ return res.status(401).json({ message: 'Invalid slug' })
+ }
+
+ // Enable Preview Mode by setting the cookies
+ res.setPreviewData({})
+
+ // Redirect to the path from the fetched post
+ const url = `/posts/${post.post.urlTitle}`
+ res.setHeader('Content-Type', 'text/html')
+ res.write(
+ `
+
+
+ `
+ )
+ res.end()
+}
diff --git a/examples/cms-dotcms/pages/index.tsx b/examples/cms-dotcms/pages/index.tsx
new file mode 100644
index 0000000000000..777192e1d23a3
--- /dev/null
+++ b/examples/cms-dotcms/pages/index.tsx
@@ -0,0 +1,45 @@
+import Container from '@components/container'
+import MoreStories from '@components/more-stories'
+import HeroPost from '@components/hero-post'
+import Intro from '@components/intro'
+import Layout from '@components/layout'
+import { getAllPostsForHome } from '@lib/api'
+import Head from 'next/head'
+import { CMS_NAME } from '@lib/constants'
+
+export default function Index({ preview, allPosts }) {
+ const heroPost = allPosts[0]
+ const morePosts = allPosts.slice(1)
+ const title = `Next.js Blog Example with ${CMS_NAME}`
+ return (
+ <>
+
+
+ {title}
+
+
+
+ {heroPost && (
+
+ )}
+ {morePosts.length > 0 && }
+
+
+ >
+ )
+}
+
+export async function getStaticProps({ preview = false }) {
+ const allPosts = await getAllPostsForHome(preview)
+
+ return {
+ props: { preview, allPosts },
+ }
+}
diff --git a/examples/cms-dotcms/pages/posts/[slug].tsx b/examples/cms-dotcms/pages/posts/[slug].tsx
new file mode 100644
index 0000000000000..2a74d60c4930a
--- /dev/null
+++ b/examples/cms-dotcms/pages/posts/[slug].tsx
@@ -0,0 +1,82 @@
+import { useRouter } from 'next/router'
+import Head from 'next/head'
+import ErrorPage from 'next/error'
+import Container from '@components/container'
+import MoreStories from '@components/more-stories'
+import Header from '@components/header'
+import PostHeader from '@components/post-header'
+import PostBody from '@components/post-body'
+import SectionSeparator from '@components/section-separator'
+import Layout from '@components/layout'
+import PostTitle from '@components/post-title'
+import { CMS_NAME } from '@lib/constants'
+import { getAllPostsWithSlug, getPostAndMorePosts } from '@lib/api'
+
+export default function Post({ post, morePosts, preview }) {
+ const router = useRouter()
+
+ if (!router.isFallback && !post) {
+ return
+ }
+
+ const title = `${
+ post?.title || 'dotcms'
+ } | Next.js Blog Example with ${CMS_NAME}`
+
+ return (
+
+
+
+ {router.isFallback ? (
+ Loading…
+ ) : (
+ <>
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+ {morePosts && morePosts.length > 0 && (
+
+ )}
+ >
+ )}
+
+
+ )
+}
+
+export async function getStaticProps({ params, preview = false }) {
+ const data = await getPostAndMorePosts(params.slug, preview)
+
+ return {
+ props: {
+ preview,
+ ...data,
+ },
+ }
+}
+
+export async function getStaticPaths() {
+ const allPosts = await getAllPostsWithSlug()
+
+ return {
+ paths: allPosts?.map((post) => `/posts/${post.urlTitle}`) || [],
+ fallback: true,
+ }
+}
diff --git a/examples/cms-dotcms/postcss.config.js b/examples/cms-dotcms/postcss.config.js
new file mode 100644
index 0000000000000..3fa0a9514dc9d
--- /dev/null
+++ b/examples/cms-dotcms/postcss.config.js
@@ -0,0 +1,8 @@
+// If you want to use other PostCSS plugins, see the following:
+// https://tailwindcss.com/docs/using-with-preprocessors
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/examples/cms-dotcms/public/favicon/android-chrome-192x192.png b/examples/cms-dotcms/public/favicon/android-chrome-192x192.png
new file mode 100644
index 0000000000000..2f07282a59cda
Binary files /dev/null and b/examples/cms-dotcms/public/favicon/android-chrome-192x192.png differ
diff --git a/examples/cms-dotcms/public/favicon/android-chrome-512x512.png b/examples/cms-dotcms/public/favicon/android-chrome-512x512.png
new file mode 100644
index 0000000000000..dbb0faea84049
Binary files /dev/null and b/examples/cms-dotcms/public/favicon/android-chrome-512x512.png differ
diff --git a/examples/cms-dotcms/public/favicon/apple-touch-icon.png b/examples/cms-dotcms/public/favicon/apple-touch-icon.png
new file mode 100644
index 0000000000000..8f4033b2a8b35
Binary files /dev/null and b/examples/cms-dotcms/public/favicon/apple-touch-icon.png differ
diff --git a/examples/cms-dotcms/public/favicon/browserconfig.xml b/examples/cms-dotcms/public/favicon/browserconfig.xml
new file mode 100644
index 0000000000000..9824d87b11517
--- /dev/null
+++ b/examples/cms-dotcms/public/favicon/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ #000000
+
+
+
diff --git a/examples/cms-dotcms/public/favicon/favicon-16x16.png b/examples/cms-dotcms/public/favicon/favicon-16x16.png
new file mode 100644
index 0000000000000..29deaf6716e77
Binary files /dev/null and b/examples/cms-dotcms/public/favicon/favicon-16x16.png differ
diff --git a/examples/cms-dotcms/public/favicon/favicon-32x32.png b/examples/cms-dotcms/public/favicon/favicon-32x32.png
new file mode 100644
index 0000000000000..e3b4277bf093d
Binary files /dev/null and b/examples/cms-dotcms/public/favicon/favicon-32x32.png differ
diff --git a/examples/cms-dotcms/public/favicon/favicon.ico b/examples/cms-dotcms/public/favicon/favicon.ico
new file mode 100644
index 0000000000000..ea2f437d9db65
Binary files /dev/null and b/examples/cms-dotcms/public/favicon/favicon.ico differ
diff --git a/examples/cms-dotcms/public/favicon/mstile-150x150.png b/examples/cms-dotcms/public/favicon/mstile-150x150.png
new file mode 100644
index 0000000000000..f2dfd904bf1be
Binary files /dev/null and b/examples/cms-dotcms/public/favicon/mstile-150x150.png differ
diff --git a/examples/cms-dotcms/public/favicon/safari-pinned-tab.svg b/examples/cms-dotcms/public/favicon/safari-pinned-tab.svg
new file mode 100644
index 0000000000000..72ab6e050cb11
--- /dev/null
+++ b/examples/cms-dotcms/public/favicon/safari-pinned-tab.svg
@@ -0,0 +1,33 @@
+
+
+
+
+Created by potrace 1.11, written by Peter Selinger 2001-2013
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/cms-dotcms/public/favicon/site.webmanifest b/examples/cms-dotcms/public/favicon/site.webmanifest
new file mode 100644
index 0000000000000..a672d9a233c59
--- /dev/null
+++ b/examples/cms-dotcms/public/favicon/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "Next.js",
+ "short_name": "Next.js",
+ "icons": [
+ {
+ "src": "/favicons/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/favicons/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#000000",
+ "background_color": "#000000",
+ "display": "standalone"
+}
diff --git a/examples/cms-dotcms/styles/index.css b/examples/cms-dotcms/styles/index.css
new file mode 100644
index 0000000000000..b63c4592cb2e1
--- /dev/null
+++ b/examples/cms-dotcms/styles/index.css
@@ -0,0 +1,5 @@
+/* purgecss start ignore */
+@tailwind base;
+@tailwind components;
+/* purgecss end ignore */
+@tailwind utilities;
diff --git a/examples/cms-dotcms/tailwind.config.js b/examples/cms-dotcms/tailwind.config.js
new file mode 100644
index 0000000000000..031eb4ff96602
--- /dev/null
+++ b/examples/cms-dotcms/tailwind.config.js
@@ -0,0 +1,48 @@
+module.exports = {
+ content: [
+ './pages/**/*.{js,ts,jsx,tsx}',
+ './components/**/*.{js,ts,jsx,tsx}',
+ ],
+ theme: {
+ extend: {
+ container: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ },
+ },
+ colors: {
+ 'accent-1': '#FAFAFA',
+ 'accent-2': '#EAEAEA',
+ 'accent-7': '#333',
+ success: '#0070f3',
+ cyan: '#79FFE1',
+ },
+ spacing: {
+ 28: '7rem',
+ },
+ letterSpacing: {
+ tighter: '-.04em',
+ },
+ lineHeight: {
+ tight: 1.2,
+ },
+ fontSize: {
+ '5xl': '2.5rem',
+ '6xl': '2.75rem',
+ '7xl': '4.5rem',
+ '8xl': '6.25rem',
+ },
+ boxShadow: {
+ small: '0 5px 10px rgba(0, 0, 0, 0.12)',
+ medium: '0 8px 30px rgba(0, 0, 0, 0.12)',
+ },
+ },
+ },
+ variants: {
+ extend: {},
+ },
+ plugins: [require('@tailwindcss/typography')],
+}
diff --git a/examples/cms-dotcms/tsconfig.json b/examples/cms-dotcms/tsconfig.json
new file mode 100644
index 0000000000000..d9c8e08b5ffd7
--- /dev/null
+++ b/examples/cms-dotcms/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "baseUrl": ".",
+ "paths": {
+ "@components/*": ["components/*"],
+ "@lib/*": ["lib/*"],
+ "@styles/*": ["styles/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}
diff --git a/examples/cms-drupal/README.md b/examples/cms-drupal/README.md
index 6c1b7d7a0af3f..ed0798c9cfd8c 100644
--- a/examples/cms-drupal/README.md
+++ b/examples/cms-drupal/README.md
@@ -29,6 +29,7 @@ Once you have [configured the Next.js module for Drupal](https://next-drupal.org
- [Ghost](/examples/cms-ghost)
- [GraphCMS](/examples/cms-graphcms)
- [Blog Starter](/examples/blog-starter)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-ghost/README.md b/examples/cms-ghost/README.md
index 1b38b0ee0c5de..84c15e5aa78fb 100644
--- a/examples/cms-ghost/README.md
+++ b/examples/cms-ghost/README.md
@@ -28,6 +28,7 @@ Once you have access to [the environment variables you'll need](#step-2-set-up-e
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-graphcms/README.md b/examples/cms-graphcms/README.md
index c88cca461573c..276541789590a 100644
--- a/examples/cms-graphcms/README.md
+++ b/examples/cms-graphcms/README.md
@@ -27,6 +27,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## Deploy your own
diff --git a/examples/cms-kontent/README.md b/examples/cms-kontent/README.md
index cacd9a4fdb5b5..dae46f43153f8 100644
--- a/examples/cms-kontent/README.md
+++ b/examples/cms-kontent/README.md
@@ -30,6 +30,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-prepr/README.md b/examples/cms-prepr/README.md
index 754fd83b07031..66539d59c55ba 100644
--- a/examples/cms-prepr/README.md
+++ b/examples/cms-prepr/README.md
@@ -26,6 +26,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
- [Ghost](/examples/cms-ghost)
- [GraphCMS](/examples/cms-graphcms)
- [Blog Starter](/examples/blog-starter)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## Getting Started
diff --git a/examples/cms-prismic/README.md b/examples/cms-prismic/README.md
index fcf186bd1787f..74481d7d0707f 100644
--- a/examples/cms-prismic/README.md
+++ b/examples/cms-prismic/README.md
@@ -29,6 +29,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-sanity/README.md b/examples/cms-sanity/README.md
index 53d2052c6bae3..81402ef7b0120 100644
--- a/examples/cms-sanity/README.md
+++ b/examples/cms-sanity/README.md
@@ -31,6 +31,7 @@ You'll get:
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
# Configuration
diff --git a/examples/cms-storyblok/README.md b/examples/cms-storyblok/README.md
index 47e6a35284a3f..03658512cbbe6 100644
--- a/examples/cms-storyblok/README.md
+++ b/examples/cms-storyblok/README.md
@@ -30,6 +30,7 @@ Once you have access to [the environment variables you'll need](#step-6-set-up-e
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-strapi/README.md b/examples/cms-strapi/README.md
index 3b9dd856d0be6..546964f9a94f4 100644
--- a/examples/cms-strapi/README.md
+++ b/examples/cms-strapi/README.md
@@ -30,6 +30,7 @@ Once you have access to [the environment variables you'll need](#step-7-set-up-e
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-takeshape/README.md b/examples/cms-takeshape/README.md
index a763a80311f27..7d8cdff77e222 100644
--- a/examples/cms-takeshape/README.md
+++ b/examples/cms-takeshape/README.md
@@ -29,6 +29,7 @@ Once you have access to [the environment variables you'll need](#step-5-set-up-e
- [Ghost](/examples/cms-ghost)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-tina/README.md b/examples/cms-tina/README.md
index 5762fd64109ac..5b181b8f518c4 100644
--- a/examples/cms-tina/README.md
+++ b/examples/cms-tina/README.md
@@ -26,6 +26,7 @@ This example showcases Next.js's [Static Generation](https://nextjs.org/docs/bas
- [Umbraco Heartcore](/examples/cms-umbraco-heartcore)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-umbraco-heartcore/README.md b/examples/cms-umbraco-heartcore/README.md
old mode 100755
new mode 100644
index 7f232f9147013..3e78330b06b03
--- a/examples/cms-umbraco-heartcore/README.md
+++ b/examples/cms-umbraco-heartcore/README.md
@@ -30,6 +30,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
- [Kontent](/examples/cms-kontent)
- [Ghost](/examples/cms-ghost)
- [Blog Starter](/examples/blog-starter)
+- [DotCMS](/examples/cms-dotcms)
- [Enterspeed](/examples/cms-enterspeed)
## How to use
diff --git a/examples/cms-wordpress/README.md b/examples/cms-wordpress/README.md
index 632bcda74dcad..47747da613450 100644
--- a/examples/cms-wordpress/README.md
+++ b/examples/cms-wordpress/README.md
@@ -29,6 +29,7 @@ Once you have access to [the environment variables you'll need](#step-3-set-up-e
- [Ghost](/examples/cms-ghost)
- [Blog Starter](/examples/blog-starter)
- [Builder.io](/examples/cms-builder-io)
+- [DotCMS](/examples/cms-dotcms)
## How to use