From 480f37402df70403e20ea3e4f70a293fb662434c Mon Sep 17 00:00:00 2001 From: Christopher Hlubek Date: Mon, 29 Apr 2024 12:14:13 +0200 Subject: [PATCH] Update readme --- README.md | 142 +++++++++++++++++++++++---------------- docs/zebra_logo_dark.svg | 20 ++++++ 2 files changed, 103 insertions(+), 59 deletions(-) create mode 100644 docs/zebra_logo_dark.svg diff --git a/README.md b/README.md index d071c95..f153942 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,74 @@ -[![npm version](https://badge.fury.io/js/@networkteam%2Fzebra.svg)](https://badge.fury.io/js/@networkteam%2Fzebra) -# @networkteam/zebra + -🦓 +

+
+ logo of zebra +
+
+

-## Why? +

@networkteam/zebra

+ +

+A package for Next.js to use Neos CMS as a headless CMS with full visual editing.
+

-* Neos is a great CMS with flexible content structures and focus on a streamlined editing experience -* Next.js offers a great developer experience and a way to build modern websites and applications with a mixed form of static and dynamic pages + +NPM version + +

-So why not combine the best of both worlds? + -Our question was: Can we retain the editing experience of Neos while using Next.js for the frontend? And the answer is: Yes, we can! +## Why? + +* Neos is a great CMS with flexible content structures and focus on a streamlined, visual editing experience. +* Next.js offers a great developer experience and a full-stack framework to build modern websites and applications. ## Features -* No frontend rendering in Neos CMS - it's used headless, besides providing the Neos UI -* Use React components for rendering the frontend based on content (node properties) from Neos - your own components with a few helpers and hooks for editing +* Supports Next.js >= 12.2 with app or pages router +* Full support for content rendering via React server components (RSC) +* Provides components and helpers to load and render content from Neos CMS via React components * Full editing and preview capabilities in the Neos UI using the frontend generated via Next.js * Use multi-language sites with Neos and Next.js * Supports multi-site setups (single Neos with sites, multiple Next.js instances) +* App code and content can be mixed ## How does it work? -This package is used inside a Next.js project that fetches content from Neos CMS for rendering and offers editing with full preview capabilities. It provides components and hooks to handle the rendering of nodes and adding editing metadata for the Neos UI. +This package is used inside a Next.js project that fetches content from Neos CMS for rendering and offers editing with full preview capabilities. It provides components and helpers to handle the loading and rendering of nodes. It adds the necessary editing metadata for the Neos UI to work as in a traditional Neos setup. Inside Neos CMS a few supporting packages are used to provide the content via an API for Next.js and adjust the behavior of the Neos UI: * [Networkteam.Neos.ContentApi](https://github.com/networkteam/Networkteam.Neos.ContentApi) for providing the content via a JSON API. * [Networkteam.Neos.Next](https://github.com/networkteam/Networkteam.Neos.Next) for integrating Next.js as a preview of nodes and handle revalidation of changed documents on publishing. -We also published some supporting tools: - -* [github.com/networkteam/grazer](https://github.com/networkteam/grazer) is an HTTP service implementing a specialized priority queue to revalidate pages for changed documents reliably. - ## Installation +> Note: this readme focuses on using Zebra with the Next.js app router, as it is a more flexible approach for data-loading and supports React server components. + * Create or use an existing Next.js project * Add `@networkteam/zebra` to your project -* Apply `withZebra` to your `next.config.mjs`: +* Apply `withZebra` to your `next.config.js`: ```js - import { withZebra } from '@networkteam/zebra'; + /** @type {import('next').NextConfig} */ + + const { withZebra } = require('@networkteam/zebra'); + + const nextConfig = { + reactStrictMode: true, + // ... + }; - export default withZebra({ - // your next config - }); + module.exports = withZebra(nextConfig); ``` * Create a few pages and an API route for revalidation: - * [`pages/[[...slug]].tsx`](https://github.com/networkteam/zebra-demo/blob/main/next/pages/[[...slug]].tsx) - * [`pages/neos/preview.tsx`](https://github.com/networkteam/zebra-demo/blob/main/next/pages/neos/preview.tsx) - * [`pages/neos/previewNode.tsx`](https://github.com/networkteam/zebra-demo/blob/main/next/pages/neos/previewNode.tsx) - * [`pages/api/revalidate.ts`](https://github.com/networkteam/zebra-demo/blob/main/next/pages/api/revalidate.ts) -* Configure a custom document or add `` to your existing [`pages/_document.tsx`](https://github.com/networkteam/zebra-demo/blob/main/next/pages/_document.tsx) -* Set the environment variable `NEOS_BASE_URL` to your Neos installation + * [`app/[[...slug]]/page.tsx`](https://github.com/networkteam/zebra-demo/blob/main/next/app/[[...slug]]/page.tsx) + * [`app/neos/preview/page.tsx`](https://github.com/networkteam/zebra-demo/blob/main/next/app/neos/preview/page.tsx) + * [`app/neos/previewNode/page.tsx`](https://github.com/networkteam/zebra-demo/blob/main/next/app/neos/previewNode/page.tsx) + * [`app/api/revalidate/route.ts`](https://github.com/networkteam/zebra-demo/blob/main/next/app/api/revalidate/route.ts) +* Set the environment variable `NEOS_BASE_URL` to your Neos installation and `PUBLIC_BASE_URL` to the public URL of your Next.js site. ## Further reading @@ -62,6 +79,7 @@ See the **demo project** for a working example: And here's a list of articles with more background: * [Zebra: Full editing with Neos and Next.js](https://networkteam.com/journal/2023/zebra-neos-and-next) +* [Zebra: A preview of Next.js 13 App Router and React Server Components](https://networkteam.com/journal/2023/zebra-nextjs-app-router-and-server-components) ## Configuration @@ -70,49 +88,51 @@ And here's a list of articles with more background: * `NEOS_BASE_URL`: The base URL of your Neos installation. This could be an internal URL that is not reachable from outside. * `PUBLIC_BASE_URL`: The base URL of your Next.js site. This is the URL where your website will be reachable from outside. * `REVALIDATE_TOKEN`: A secret token that will be used to validate calls to the revalidate API route. -* `REVALIDATE_CONCURRENCY`: How many concurrent revalidations should be performed. Defaults to `2`. ## Rendering content -Zebra provides a `Frontend` component to render a document from Neos. -You provide a mapping from node types to React components. -It is good practice to split components in *presentational* (no knowledge about Neos) and *integrational* components (adds Zebra components and hooks for editing capabilities on top of *presentational* components). +Zebra provides a `NodeRenderer` component to render a node from Neos. +You provide a mapping from node types to React components via `initNodeTypes` which should be imported in your root layout. +It is good practice to split components in *presentational* (no knowledge about Neos) and *content* components (adds Zebra components and helpers for editing capabilities on top of *presentational* components). + +> Note: as React server components do not support `useContext`, we explicitly pass the `ctx` object to all components that need to access content. ### Example -Define node type mappings: +**Define node type mappings (`lib/config/nodeTypes.ts`):** ```tsx +import { initNodeTypes } from '@networkteam/zebra/server'; + import DocumentPage from '../components/document/Page'; import ContentHeadline from '../components/content/Headline'; -export const nodeTypes = { +initNodeTypes({ // Documents 'MyProject.Site:Document.Page': DocumentPage, // Content 'Neos.NodeTypes:Headline': ContentHeadline, -}; +}); ``` -Component for a basic document page: +**Add a component for a basic document page (`lib/components/document/Page.tsx`):** ```tsx -import { - ContentCollection, - withMeta, -} from '@networkteam/zebra/server'; +import { ContextProps } from '@networkteam/zebra'; +import { ContentCollection } from '@networkteam/zebra/server'; import Header from './partials/Header'; -const DocumentPage = async ({ ctx }: { ctx: ContextProps }) => { - const meta = withMeta(ctx); +const DocumentPage = ({ ctx }: { ctx: ContextProps }) => { + const { mainNavigation } = await withMeta(ctx); const inBackend = ctx.inBackend; return (
@@ -126,7 +146,7 @@ const DocumentPage = async ({ ctx }: { ctx: ContextProps }) => { export default DocumentPage; ``` -Presentational component for a headline: +**Add a presentational component for a headline:** ```tsx import classNames from 'classnames'; @@ -161,7 +181,7 @@ const Headline = ({ children, as: Component = 'h1', size, className }: HeadlineP export default Headline; ``` -Integrational component for a headline: +**Add a content component for a headline:** ```tsx import { ContextProps } from '@networkteam/zebra'; @@ -176,7 +196,7 @@ const ContentHeadline = async ({ ctx }: { ctx: ContextProps }) => { return ( - + @@ -186,18 +206,20 @@ const ContentHeadline = async ({ ctx }: { ctx: ContextProps }) => { export default ContentHeadline; ``` -### Static site generation +### Document rendering
This is how the public view of the Next.js site is generated from content in Neos. - Your Next.js project defines a [dynamic catch all route](https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes) that will generate pages for document nodes in Neos CMS. The route is defined in `pages/[[...slug]].tsx`. + Your Next.js project defines a [dynamic route with an optional catch-all segment](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#optional-catch-all-segments) that will render the page for a document node in Neos CMS. The route is defined in `app/[[...slug]]/page.tsx`. + + Next.js will fetch the node data for a document node from Neos via the Content API in `loadDocumentPropsCached`. You can implement custom processing based on the loaded data, e.g. to fetch additional data, handle shortcuts and redirects or to handle errors. - Next.js will fetch a list of all document nodes from Neos via the Content API in `getStaticProps` for the `[[...slug]]` route. + Using the `NodeRenderer` a new `ctx` prop is passed with information about the current document / node identifier and route path as well as the data loader options for subsequent requests. This will be passed on to all React server components render content. The identifier will be changed while iterating over individual nodes (e.g. content collections). - The `getStaticProps` function will then be called for each page with the `path` and `locale` as params. - The data for the page will be fetched via the Content API in Neos by the path and locale. - This data is the input for rendering the page, so the response of the Content API needs to contain all needed information like menu items, shared content in e.g. a footer and the content of the page itself. + This data is the input for rendering the page, so the response of the Content API needs to contain needed information like menu items, shared content in e.g. a footer and the content of the page itself. With React server components you could also fetch additional data in the component itself. + + Next.js takes care of deduplicating identical fetch requests, so each component in the render tree can fetch data independently without causing additional requests. For this to work, the Neos base URL has to be known to Next.js via the `NEOS_BASE_URL` environment variable.
@@ -211,26 +233,24 @@ export default ContentHeadline; You always access Neos CMS via your Next.js site by appending `/neos`, as usual. The `withZebra` config helper adds the necessary rewrites to the Next.js configuration in `next.config.js` to make this work. - Next.js serves a custom `/neos/preview` route that is used to render the preview of a workspace version of a document node. - It forwards your Neos session cookie to the Neos backend and fetches the content via the Content API - now with access to the user workspace and much more metadata for use in Neos UI. - - This is not done statically - as it would not allow to access the current request and user session - but on demand via `getServerSideProps`. + Next.js serves a custom `/neos/preview` route that is used to render the preview of a document node in the user workspace. + It forwards your Neos session cookie to the Neos backend and fetches the content via the Content API - now with access to the user workspace and more metadata for use in Neos UI. - By using the Zebra components and hooks for rendering, all the metadata for the Neos UI is added to the page. Inline editing should just work. + By using the Zebra components and helpers for rendering, all the metadata for the Neos UI is added to the page. Inline editing should just work. All other requests to `/neos/*` (except `/neos/previewNode`) are proxied to the Neos backend. -### Revalidation +### Caching and revalidation
- This is how incremental static regeneration (ISR) is used if content changes are published in Neos. + This is how on-demand revalidation is used if content changes are published in Neos. - This is done by the [Networkteam.Neos.Next](https://github.com/networkteam/Networkteam.Neos.Next) package in Neos. It hooks into the publishing signals, collects changed nodes and their closest document nodes and triggers a revalidation of the pages via a Next.js API route (defaults to `/api/revalidate`). A revalidate token is used to prevent unauthorized revalidation requests. + This is done by the [Networkteam.Neos.Next](https://github.com/networkteam/Networkteam.Neos.Next) package in Neos. It hooks into the publishing signals, collects changed nodes and their closest document nodes and triggers a revalidation of the routes via a Next.js route handler (defaults to `/api/revalidate`). A revalidate token is used to prevent unauthorized revalidation requests. Note: For this to work, the Next.js base URL has to be known inside Neos. - Since content often depends on other documents (e.g. document titles in navigation, teaser cards, etc.), it is advised to implement a _full revalidation_ after every change. This is why we developed [grazer](https://github.com/networkteam/grazer): it receives revalidate requests from Neos at `/api/revalidate` and handles revalidation requests of all other documents to Next.js in the background. It uses a priority queue that prioritizes older and explicit revalidate route paths before other route paths that are revalidated for consistency. + Since content often depends on other documents (e.g. document titles in navigation, teaser cards, etc.), it is advised to implement a _full revalidation_ after every change. With the app router, this will only mark routes as invalidated and they will be freshly rendered on the next request. Note: This approach works reasonably well and solves a lot of complexity with dependencies and figuring out an _exact_ set of document to revalidate.
@@ -282,6 +302,10 @@ TODO Write more about deployment of a Neos / Next project * Next.js needs to know about the publicly used base URL via the `PUBLIC_BASE_URL` env var to make sure URIs are generated correctly for revalidate calls to the content API in Neos. * Your Neos will need to accept proxy headers from Next.js, make sure to allow it in `Neos.Flow.http.trustedProxies` in `Settings.yaml`. +## Pages router + +Please have a look at https://github.com/networkteam/zebra/blob/v0.9.0/README.md to see instructions for using the pages router. + ## Contributing We are happy to accept contributions. Just open an issue or pull request. diff --git a/docs/zebra_logo_dark.svg b/docs/zebra_logo_dark.svg new file mode 100644 index 0000000..b8c08e4 --- /dev/null +++ b/docs/zebra_logo_dark.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + +