From 38d1987749325cc3a515dd2399f3c1113a29d69b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 29 Aug 2024 23:46:17 +0200 Subject: [PATCH] examples: Add `cms-umbraco` example (#52777) Co-authored-by: Lee Robinson --- examples/cms-umbraco/.env.local.example | 11 ++ examples/cms-umbraco/.gitignore | 35 ++++ examples/cms-umbraco/README.md | 161 ++++++++++++++++++ examples/cms-umbraco/components/alert.tsx | 46 +++++ examples/cms-umbraco/components/avatar.tsx | 24 +++ examples/cms-umbraco/components/container.tsx | 7 + .../cms-umbraco/components/cover-image.tsx | 35 ++++ examples/cms-umbraco/components/date.tsx | 10 ++ examples/cms-umbraco/components/footer.tsx | 30 ++++ examples/cms-umbraco/components/header.tsx | 12 ++ examples/cms-umbraco/components/hero-post.tsx | 55 ++++++ examples/cms-umbraco/components/intro.tsx | 28 +++ examples/cms-umbraco/components/layout.tsx | 21 +++ examples/cms-umbraco/components/meta.tsx | 41 +++++ .../cms-umbraco/components/more-stories.tsx | 29 ++++ .../components/post-body.module.css | 76 +++++++++ examples/cms-umbraco/components/post-body.tsx | 16 ++ .../cms-umbraco/components/post-header.tsx | 35 ++++ .../cms-umbraco/components/post-preview.tsx | 47 +++++ .../cms-umbraco/components/post-title.tsx | 12 ++ .../components/section-separator.tsx | 3 + examples/cms-umbraco/components/tags.tsx | 18 ++ examples/cms-umbraco/lib/api.ts | 113 ++++++++++++ examples/cms-umbraco/lib/constants.ts | 3 + examples/cms-umbraco/next-env.d.ts | 5 + examples/cms-umbraco/next.config.js | 6 + examples/cms-umbraco/package.json | 23 +++ examples/cms-umbraco/pages/_app.tsx | 8 + .../cms-umbraco/pages/api/exit-preview.ts | 10 ++ examples/cms-umbraco/pages/api/preview.ts | 24 +++ examples/cms-umbraco/pages/index.tsx | 54 ++++++ examples/cms-umbraco/pages/posts/[slug].tsx | 97 +++++++++++ examples/cms-umbraco/postcss.config.js | 8 + .../public/favicon/android-chrome-192x192.png | Bin 0 -> 4795 bytes .../public/favicon/android-chrome-512x512.png | Bin 0 -> 14640 bytes .../public/favicon/apple-touch-icon.png | Bin 0 -> 1327 bytes .../public/favicon/browserconfig.xml | 9 + .../public/favicon/favicon-16x16.png | Bin 0 -> 595 bytes .../public/favicon/favicon-32x32.png | Bin 0 -> 880 bytes .../cms-umbraco/public/favicon/favicon.ico | Bin 0 -> 15086 bytes .../public/favicon/mstile-150x150.png | Bin 0 -> 3567 bytes .../public/favicon/safari-pinned-tab.svg | 33 ++++ .../public/favicon/site.webmanifest | 19 +++ examples/cms-umbraco/styles/index.css | 104 +++++++++++ examples/cms-umbraco/tailwind.config.js | 37 ++++ examples/cms-umbraco/tsconfig.json | 20 +++ examples/cms-umbraco/types/author.ts | 9 + examples/cms-umbraco/types/picture.ts | 5 + examples/cms-umbraco/types/post.ts | 16 ++ .../cms-umbraco/types/postAndMorePosts.ts | 8 + 50 files changed, 1363 insertions(+) create mode 100644 examples/cms-umbraco/.env.local.example create mode 100644 examples/cms-umbraco/.gitignore create mode 100644 examples/cms-umbraco/README.md create mode 100644 examples/cms-umbraco/components/alert.tsx create mode 100644 examples/cms-umbraco/components/avatar.tsx create mode 100644 examples/cms-umbraco/components/container.tsx create mode 100644 examples/cms-umbraco/components/cover-image.tsx create mode 100644 examples/cms-umbraco/components/date.tsx create mode 100644 examples/cms-umbraco/components/footer.tsx create mode 100644 examples/cms-umbraco/components/header.tsx create mode 100644 examples/cms-umbraco/components/hero-post.tsx create mode 100644 examples/cms-umbraco/components/intro.tsx create mode 100644 examples/cms-umbraco/components/layout.tsx create mode 100644 examples/cms-umbraco/components/meta.tsx create mode 100644 examples/cms-umbraco/components/more-stories.tsx create mode 100644 examples/cms-umbraco/components/post-body.module.css create mode 100644 examples/cms-umbraco/components/post-body.tsx create mode 100644 examples/cms-umbraco/components/post-header.tsx create mode 100644 examples/cms-umbraco/components/post-preview.tsx create mode 100644 examples/cms-umbraco/components/post-title.tsx create mode 100644 examples/cms-umbraco/components/section-separator.tsx create mode 100644 examples/cms-umbraco/components/tags.tsx create mode 100644 examples/cms-umbraco/lib/api.ts create mode 100644 examples/cms-umbraco/lib/constants.ts create mode 100644 examples/cms-umbraco/next-env.d.ts create mode 100644 examples/cms-umbraco/next.config.js create mode 100644 examples/cms-umbraco/package.json create mode 100644 examples/cms-umbraco/pages/_app.tsx create mode 100644 examples/cms-umbraco/pages/api/exit-preview.ts create mode 100644 examples/cms-umbraco/pages/api/preview.ts create mode 100644 examples/cms-umbraco/pages/index.tsx create mode 100644 examples/cms-umbraco/pages/posts/[slug].tsx create mode 100644 examples/cms-umbraco/postcss.config.js create mode 100644 examples/cms-umbraco/public/favicon/android-chrome-192x192.png create mode 100644 examples/cms-umbraco/public/favicon/android-chrome-512x512.png create mode 100644 examples/cms-umbraco/public/favicon/apple-touch-icon.png create mode 100644 examples/cms-umbraco/public/favicon/browserconfig.xml create mode 100644 examples/cms-umbraco/public/favicon/favicon-16x16.png create mode 100644 examples/cms-umbraco/public/favicon/favicon-32x32.png create mode 100644 examples/cms-umbraco/public/favicon/favicon.ico create mode 100644 examples/cms-umbraco/public/favicon/mstile-150x150.png create mode 100644 examples/cms-umbraco/public/favicon/safari-pinned-tab.svg create mode 100644 examples/cms-umbraco/public/favicon/site.webmanifest create mode 100644 examples/cms-umbraco/styles/index.css create mode 100644 examples/cms-umbraco/tailwind.config.js create mode 100644 examples/cms-umbraco/tsconfig.json create mode 100644 examples/cms-umbraco/types/author.ts create mode 100644 examples/cms-umbraco/types/picture.ts create mode 100644 examples/cms-umbraco/types/post.ts create mode 100644 examples/cms-umbraco/types/postAndMorePosts.ts diff --git a/examples/cms-umbraco/.env.local.example b/examples/cms-umbraco/.env.local.example new file mode 100644 index 0000000000000..ed269785462cb --- /dev/null +++ b/examples/cms-umbraco/.env.local.example @@ -0,0 +1,11 @@ +# This is necessary when you run locally against a self-signed server. Do NOT include this in production. +NODE_TLS_REJECT_UNAUTHORIZED=0 + +# Add your Umbraco server URL here. Please do not include a trailing slash. +UMBRACO_SERVER_URL = + +# Add your Umbraco Delivery API key here if you want to use preview. +UMBRACO_DELIVERY_API_KEY = + +# Add the secret token that will be used to "authorize" preview +UMBRACO_PREVIEW_SECRET = diff --git a/examples/cms-umbraco/.gitignore b/examples/cms-umbraco/.gitignore new file mode 100644 index 0000000000000..8f322f0d8f495 --- /dev/null +++ b/examples/cms-umbraco/.gitignore @@ -0,0 +1,35 @@ +# 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 + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/examples/cms-umbraco/README.md b/examples/cms-umbraco/README.md new file mode 100644 index 0000000000000..d186382a8bed3 --- /dev/null +++ b/examples/cms-umbraco/README.md @@ -0,0 +1,161 @@ +# A statically generated blog example using Next.js and Umbraco CMS + +This example showcases Next.js's [Static Generation](https://nextjs.org/docs/basic-features/pages) feature using [Umbraco CMS](https://www.umbraco.com/) as the data source. + +## Demo + +### https://nextjs-umbraco-sample-blog.vercel.app/ + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npx create-next-app --example cms-umbraco umbraco-app +# or +yarn create next-app --example cms-umbraco umbraco-app +# or +pnpm create next-app --example cms-umbraco umbraco-app +``` + +## Configuration + +### Step 1. Create an Umbraco project + +Use the .NET CLI to create a project locally. + +1. Create an empty folder and open a terminal there. +2. If you haven't already, install the Umbraco .NET CLI templates for version 12.0 or above by running: `dotnet new install Umbraco.Templates::13.*`. +3. Create the Umbraco project by running: `dotnet new umbraco` + +For more information on the Umbraco .NET CLI templates, visit [this page](https://docs.umbraco.com/umbraco-cms/fundamentals/setup/install/install-umbraco-with-templates). + +### Step 2. Install sample data + +To avoid having to create the entire blog dataset in hand, we have created a [NuGet package](https://www.nuget.org/packages/Umbraco.Sample.Headless.Blog) with everything you need to get started. + +Install the NuGet package with the following command in the terminal window: `dotnet add package Umbraco.Sample.Headless.Blog`. + +### Step 3. Configure the Umbraco Delivery API + +The Umbraco Delivery API will be the data source for the blog. This API must be enabled explicitly. + +Open `appsettings.json` and add the `DeliveryApi` configuration inside `Umbraco::CMS`: + +```json + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true, + "ApiKey": "my-secret-api-key" + }, + .... +``` + +_The `ApiKey` configuration is optional, though necessary if you want to use the preview functionality of the blog sample._ + +### Step 4. Run Umbraco + +Start Umbraco with the following command in the terminal window: `dotnet run`. + +Follow the installation wizard to complete the Umbraco setup. + +Once completed you'll be redirected to the Umbraco backoffice where the blog sample data is already installed. + +### Step 5. Publish the sample data + +All the sample content is unpublished to begin with. You need to publish all of it to show the blog posts on the blog. + +1. Click the _Posts_ item in the Content tree. This item contains all the individual blog posts. +2. In the lower right hand corner of the browser you'll find a green button labelled "Save and publish". +3. Click the little up-arrow next to this button and select "Publish with descendants...". +4. In the dialog, tick "Include unpublished content items" to publish the _Posts_ item and all the blog posts in one go. + +Now do the same for the _Authors_ item. + +### Step 6. Set up environment variables + +Locate `.env.local.example` where you created the `umbraco-app` project. Create a copy of the file and name it `.env.local`. Now edit the file and fill in the blanks. + +- `UMBRACO_SERVER_URL`: The base URL of your Umbraco site. Avoid trailing slashes here. +- `UMBRACO_DELIVERY_API_KEY`: The API key you configured in `appsettings.json`. This is only necessary if you want to test Preview Mode. +- `UMBRACO_PREVIEW_SECRET` This can be any random string (but avoid spaces), like `my-preview-secret`. This is used for triggering preview, thus only necessary if you want to test Preview Mode. + +The file should end up looking something like this: + +``` +NODE_TLS_REJECT_UNAUTHORIZED=0 +UMBRACO_SERVER_URL = 'https://localhost:12345' +UMBRACO_DELIVERY_API_KEY = 'my-secret-api-key' +UMBRACO_PREVIEW_SECRET = 'my-preview-secret' +``` + +Notice the `NODE_TLS_REJECT_UNAUTHORIZED=0` setting. When running a .NET website locally, a self-signed SSL certificate is created to allow HTTPS bindings. Node.js does not trust self-signed SSL certificates, so you need to bypass the SSL/TLS certificate verification with this setting. Do not use this workaround in production. + +### Step 7. Run Next.js in development mode + +In the `umbraco-app` project folder, run: + +```bash +npm install +npm run dev + +# or + +yarn install +yarn dev +``` + +Your blog should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). + +### Step 8. Try Preview Mode + +If you edit a post in Umbraco without publishing the changes, you won't see these changes at `http://localhost:3000` by default. However, if you enable **Preview Mode**, you'll be able to see the changes ([Documentation](https://nextjs.org/docs/advanced-features/preview-mode)). + +To enable Preview Mode, go to this URL: + +``` +http://localhost:3000/api/preview?secret= +``` + +- `` should be the string you entered for `UMBRACO_PREVIEW_SECRET` in `.env.local`. + +If you browse to the changed post, you will now see the unpublished changes. + +To exit Preview Mode go to this URL: + +``` +http://localhost:3000/api/exit-preview +``` + +### Step 9. Deploy on Vercel + +You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). + +#### Deploy Umbraco + +Before you can deploy the blog to Vercel, you first need to deploy your Umbraco site to a hosting provider, to make the blog data available for Vercel. + +If you use Azure, be sure to read [the guidelines](https://docs.umbraco.com/umbraco-cms/fundamentals/setup/server-setup/azure-web-apps) on deploying Umbraco to Azure. + +You can also try this out on [Umbraco Cloud](https://umbraco.com/try-umbraco-cms/). + +#### Deploy Your Local Project + +To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example). + +**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your Umbraco deployment. + +#### Deploy from Our Template + +Alternatively, you can deploy using our template by clicking on the Deploy button below. + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/cms-umbraco&project-name=nextjs-umbraco-blog&repository-name=nextjs-umbraco-blog&env=UMBRACO_SERVER_URL,UMBRACO_DELIVERY_API_KEY,UMBRACO_PREVIEW_SECRET&envDescription=Required%20to%20connect%20the%20app%20with%20Umbraco%20CMS&envLink=https://github.com/vercel/next.js/tree/canary/examples/cms-umbraco%23step-6-set-up-environment-variables) + +### Getting to know Umbraco's Content Delivery API + +This example utilizes the native Content Delivery API in Umbraco to fetch the blog data headlessly. + +The Content Delivery API is a feature-rich API for headless content delivery. However, in an effort to keep the complexity down in this sample, certain features and optimizations have been omitted from the API queries. This results in slight over-fetching, particularly when fetching multiple blog posts. + +You can read all about the Content Delivery API [here](https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api). diff --git a/examples/cms-umbraco/components/alert.tsx b/examples/cms-umbraco/components/alert.tsx new file mode 100644 index 0000000000000..145611ec9734f --- /dev/null +++ b/examples/cms-umbraco/components/alert.tsx @@ -0,0 +1,46 @@ +import Container from "./container"; +import cn from "classnames"; +import { EXAMPLE_PATH } from "../lib/constants"; + +type Props = { + preview?: boolean; +}; + +export default function Alert({ preview }: Props) { + return ( +
+ +
+ {preview ? ( + <> + This is a page preview.{" "} + + Click here + {" "} + to exit preview mode. + + ) : ( + <> + The source code for this blog is{" "} + + available on GitHub + + . + + )} +
+
+
+ ); +} diff --git a/examples/cms-umbraco/components/avatar.tsx b/examples/cms-umbraco/components/avatar.tsx new file mode 100644 index 0000000000000..e91bf26e09744 --- /dev/null +++ b/examples/cms-umbraco/components/avatar.tsx @@ -0,0 +1,24 @@ +import Image from "next/image"; +import Author from "../types/author"; + +type Props = { + author: Author; +}; + +export default function Avatar({ author }: Props) { + const name: string = author?.name; + + return ( +
+
+ {name} +
+
{name}
+
+ ); +} diff --git a/examples/cms-umbraco/components/container.tsx b/examples/cms-umbraco/components/container.tsx new file mode 100644 index 0000000000000..4c72cdaf5e0c3 --- /dev/null +++ b/examples/cms-umbraco/components/container.tsx @@ -0,0 +1,7 @@ +type Props = { + children: React.ReactNode; +}; + +export default function Container({ children }: Props) { + return
{children}
; +} diff --git a/examples/cms-umbraco/components/cover-image.tsx b/examples/cms-umbraco/components/cover-image.tsx new file mode 100644 index 0000000000000..08493cb96407a --- /dev/null +++ b/examples/cms-umbraco/components/cover-image.tsx @@ -0,0 +1,35 @@ +import cn from "classnames"; +import Image from "next/image"; +import Link from "next/link"; +import Picture from "../types/picture"; + +type Props = { + title: string; + coverImage: Picture; + slug?: string; +}; + +export default function CoverImage({ title, coverImage, slug }: Props) { + const image = ( + {`Cover + ); + return ( +
+ {slug ? ( + + {image} + + ) : ( + image + )} +
+ ); +} diff --git a/examples/cms-umbraco/components/date.tsx b/examples/cms-umbraco/components/date.tsx new file mode 100644 index 0000000000000..bd25f18b40cae --- /dev/null +++ b/examples/cms-umbraco/components/date.tsx @@ -0,0 +1,10 @@ +import { parseISO, format } from "date-fns"; + +type Props = { + dateString: string; +}; + +export default function Date({ dateString }: Props) { + const date = parseISO(dateString); + return ; +} diff --git a/examples/cms-umbraco/components/footer.tsx b/examples/cms-umbraco/components/footer.tsx new file mode 100644 index 0000000000000..81422c7c6beb9 --- /dev/null +++ b/examples/cms-umbraco/components/footer.tsx @@ -0,0 +1,30 @@ +import Container from "./container"; +import { EXAMPLE_PATH } from "../lib/constants"; + +export default function Footer() { + return ( + + ); +} diff --git a/examples/cms-umbraco/components/header.tsx b/examples/cms-umbraco/components/header.tsx new file mode 100644 index 0000000000000..e6dea52099cd4 --- /dev/null +++ b/examples/cms-umbraco/components/header.tsx @@ -0,0 +1,12 @@ +import Link from "next/link"; + +export default function Header() { + return ( +

+ + Blog + + . +

+ ); +} diff --git a/examples/cms-umbraco/components/hero-post.tsx b/examples/cms-umbraco/components/hero-post.tsx new file mode 100644 index 0000000000000..2e8d1b206f437 --- /dev/null +++ b/examples/cms-umbraco/components/hero-post.tsx @@ -0,0 +1,55 @@ +import Avatar from "./avatar"; +import Date from "./date"; +import CoverImage from "./cover-image"; +import Link from "next/link"; +import Author from "../types/author"; +import Picture from "../types/picture"; + +type Props = { + title: string; + coverImage: Picture; + date: string; + excerpt: string; + author: Author; + slug: string; +}; + +export default function HeroPost({ + title, + coverImage, + date, + excerpt, + author, + slug, +}: Props) { + return ( +
+
+ {coverImage && ( + + )} +
+
+
+

+ +

+
+ +
+
+
+
+ +
+
+
+ ); +} diff --git a/examples/cms-umbraco/components/intro.tsx b/examples/cms-umbraco/components/intro.tsx new file mode 100644 index 0000000000000..c86fbf04e2fa3 --- /dev/null +++ b/examples/cms-umbraco/components/intro.tsx @@ -0,0 +1,28 @@ +import { EXAMPLE_TOOL_NAME, EXAMPLE_TOOL_URL } from "../lib/constants"; + +export default function Intro() { + return ( +
+

+ Blog. +

+

+ A statically generated blog example using{" "} + + Next.js + {" "} + and{" "} + + {EXAMPLE_TOOL_NAME} + + . +

+
+ ); +} diff --git a/examples/cms-umbraco/components/layout.tsx b/examples/cms-umbraco/components/layout.tsx new file mode 100644 index 0000000000000..3d43fb971cde6 --- /dev/null +++ b/examples/cms-umbraco/components/layout.tsx @@ -0,0 +1,21 @@ +import Alert from "./alert"; +import Footer from "./footer"; +import Meta from "./meta"; + +type Props = { + preview?: boolean; + children: React.ReactNode; +}; + +export default function Layout({ preview, children }: Props) { + return ( + <> + +
+ +
{children}
+
+