I'm still trying to wrap my mind around how a headless CMS like Payload works. I'd like to render a Hero block's content.
Is this the general process:
1. Define collections (sets of data), such as Pages, Media and Portfolio.
2. Within Nextjs, the [...slug].tsx renders the pages data and RenderBlocks component renders the blocks. This way pages can be created using the CMS admin panel and they'll automatically be rendered.
3. Blocks folders contain a fields config file, as well as a component file that has the front-end logic.
This is where I'm lost... how does Payload CMS import the block files (the fields and the component) to be used within the admin panel?
When I select a block to be added to a page (using the Payload dashboard) and upload the data, will it read my component file and render that block to that page automatically? So I can then navigate to that page slug and the block's content will be rendered according to the block component logic and styling? I'm wondering how that system works.
I see the Pages collection file contains an object that defines a layout and imports the blocks.
{
name: 'layout',
label: 'Page Layout',
type: 'blocks',
minRows: 1,
blocks: [
CallToAction,
Content,
Image,
CoverBlock,
],
},
How are the components being rendered? Through the RenderBlocks component? How is that logic finding the blocks components and rendering?
import React from 'react';
import { Layout } from '../../collections/Page';
import { components } from '../../blocks';
import classes from './index.module.css';
type Props = {
layout: Layout[]
className?: string
}
const RenderBlocks: React.FC<Props> = ({ layout, className }) => (
<div className={[
classes.renderBlocks,
className,
].filter(Boolean).join(' ')}
>
{layout.map((block, i) => {
const Block: React.FC<any> = components[block.blockType];
if (Block) {
return (
<section
key={i}
className={classes.block}
>
<Block {...block} />
</section>
);
}
return null;
})}
</div>
);
export default RenderBlocks;
Nextjs - Defining and Rendering Blocks
As I understand it, a
Block
in Payload context is just encapsulated configuration. How it renders in the Payload admin panel is entirely dependent on the
fields
configured within the
Block
When I select a block to be added to a page (using the Payload dashboard) and upload the data, will it read my component file and render that block to that page automatically? So I can then navigate to that page slug and the block's content will be rendered according to the block component logic and styling? I'm wondering how that system works.
By this, do you mean "render that block to that page automatically"
withinPayload or in your app?
Hi @itsjxck . Yea I'm running a Next.js Payload boilerplate so the CMS and front-end logic is in one repo. I think it's called a mono-repo.
So I have a block folder called CoverBlock, containign the field config CoverBlockFields.ts and a component 'CoverBlockComponent.tsx'.
I've configured the block fields and added it to the page config so I can add the block within a page. I'm wondering how I can connec the component to the block to fetch and render that data accordingly?
Here's the code:
// CoverBlockComponent.ts
import Image from 'next/image';
import { CoverBlockType } from './CoverBlockFields';
import RichText from '../../components/RichText';
import React from 'react';
const CoverBlockComponent: React.FC<CoverBlockType> = (props) => {
const { content, image } = props;
return (
<div>
<Image
src={`${process.env.NEXT_PUBLIC_SERVER_URL}/media/${image.filename}`}
alt={image.alt}
width={image.width}
height={image.height}
/>
<RichText content={content} />
</div>
);
};
export default CoverBlockComponent;
// blocks/CoverBlock/fields.ts
import { Block } from 'payload/types';
import { MediaType } from '../../collections/Media';
export type CoverBlockType = {
blockType: 'content',
blockName?: string,
content: unknown,
image: MediaType,
};
const CoverBlock: Block = {
slug: 'cover',
labels: {
singular: 'Cover',
plural: 'Covers',
},
fields: [
{
name: 'content',
label: 'Heading',
type: 'richText',
required: true,
},
{
name: 'feature',
label: 'Cover Image',
type: 'relationship',
relationTo: 'media',
required: true,
},
],
};
export default CoverBlock;
Ahhh, I assume you're using the Payload Nextjs boilerplate then?
yes
On the coverblocktest page that I'm rendering the block to, the image loads, but not the rich text? That's probably some basic front-end code I'm getting wrong.
Interesting. Using your examples, and the NextJs Custom Server boilerplate (
https://github.com/payloadcms/nextjs-custom-server/tree/master/blocks/Content) as reference, I think maybe the config for coverblock is slightly incorrect. Your
CoverBlockType
has
blockType: 'content'
which could be causing an issue as there is already a block defined with that type
I'd expect your config to be something like:
// blocks/CoverBlock/Config.ts
import { Block } from 'payload/types';
import { MediaType } from '../../collections/Media';
const CoverBlock: Block = {
slug: 'cover',
labels: {
singular: 'Cover',
plural: 'Covers',
},
fields: [
{
name: 'content',
label: 'Heading',
type: 'richText',
required: true,
},
{
name: 'feature',
label: 'Cover Image',
type: 'relationship',
relationTo: 'media',
required: true,
},
],
};
export default CoverBlock;
// blocks/CoverBlock/Component.tsx
import Image from 'next/image';
import { CoverBlockType } from './CoverBlockFields';
import RichText from '../../components/RichText';
import React from 'react';
export type Type = {
blockType: 'cover',
blockName?: string,
content: unknown,
image: MediaType,
};
const CoverBlockComponent: React.FC<Type> = (props) => {
const { content, image } = props;
return (
<div>
<Image
src={`${process.env.NEXT_PUBLIC_SERVER_URL}/media/${image.filename}`}
alt={image.alt}
width={image.width}
height={image.height}
/>
<RichText content={content} />
</div>
);
};
export default CoverBlockComponent;
You will need to update this file similarly
https://github.com/payloadcms/nextjs-custom-server/blob/master/blocks/index.ts
To include your new block type
The
RenderBlocks
component imports the config of your blocks here
https://github.com/payloadcms/nextjs-custom-server/blob/master/components/RenderBlocks/index.tsx#L3Then uses the
blockType
field to pick the correct component to render here
https://github.com/payloadcms/nextjs-custom-server/blob/master/components/RenderBlocks/index.tsx#L18
So your
blocks/index.ts
will looks something like this:
import { CallToAction } from './CallToAction/Config';
import { Component as cta } from './CallToAction/Component';
import { Content } from './Content/Config';
import { Component as content } from './Content/Component';
import { Image } from './Image/Config';
import { Component as image } from './Image/Component';
// Your component
import CoverBlock from './CoverBlock/Config';
import CoverBlockComponent from './CoverBlock/Component' ;
export const collections = {
CallToAction,
Content,
Image,
CoverBlock,
};
export const components = {
cta,
content,
image,
// This field name needs to match the `blockType` of your Component `Type` config
cover: CoverBlockComponent,
};
Thanks a lot @itsjxck . For some reason I included
export type CoverBlockType = {
blockType: 'content',
blockName?: string,
content: unknown,
image: MediaType,
};
I can remove that right? The rest of my code matches yours -
// blocks/CoverBlock/fields.ts
import { Block } from 'payload/types';
import { MediaType } from '../../collections/Media';
export type CoverBlockType = {
blockType: 'content',
blockName?: string,
content: unknown,
image: MediaType,
};
const CoverBlock: Block = {
slug: 'cover',
labels: {
singular: 'Cover',
plural: 'Covers',
},
fields: [
{
name: 'content',
label: 'Heading',
type: 'richText',
required: true,
},
{
name: 'feature',
label: 'Cover Image',
type: 'relationship',
relationTo: 'media',
required: true,
},
],
};
export default CoverBlock;
Oh I see you have that in the component file. I'll move it there.
You need to change the
blockType
to match the
slug
If you have this in a public repo I'd be happy to have a look and do a PR for you
So the export type
export type CoverBlockType = {
blockType: 'content',
blockName?: string,
content: unknown,
image: MediaType,
};
of the CoverBlockComponent file needs to match the slug 'cover' of the fields?
Yeah, the field
CoverBlockType.blockType
needs to be
cover
instead of
content
to match your
CoverBlock.slug
Ooh, thanks. Here's the repo:
https://github.com/taunhealy/2160-Lodge-Bukela-2
I'll appreciate you taking a look and making changes
My GH username is
itsjxck
(
https://github.com/itsjxck) if you wouldn't mind giving me access so i don't need to fork it
Sure, sent.
So the process is:
1. Define collections fields
2. Define block fields, such as the slug.
3. Create the block component and export the type, such as CoverBlockType and define the blockType as 'cover.'
4. Import and call the CoverBlock fields and component to the blocks/index.ts and define the slug 'cover' that is the block type of the component. That way Payload will know to render the component that contains the 'cover' slug
?
@Taun I updated your files to make them consistent with the other Blocks which might help you to see the differences between the provided Blocks and your custom one
Great, thanks a lot. Is the naming convention of Types etc industry standard?
I don't understand how RenderBlocks is finding [block.blockType]? How does block.blockType correlate with
export const components = {
'call-to-action': CallToAction,
'cta-grid': CTAGrid,
content: Content,
spacer: Spacer,
statistics: Statistics,
media: Media,
'media-collage': MediaCollage,
'media-content-collage': MediaContentCollage,
'media-grid': MediaGrid,
'media-stat-collage': MediaStatCollage,
'study-slider': StudySlider,
'sticky-content': StickyContent,
slider: Slider,
?
Checked you repository. You mean this:
`components[block.blockType];`
Every block you get from Payload CMS (from API call) is an object. Every block therefore has a property called
blockType
.
Property
blockType
is created from slug property you define in Payload.
When you do the below, you are simply searching for a key in a constant
components
that equals to
block.blockType
.
`components[block.blockType];`
--- Another look at the problem
When you create a block in Payload CMS with slug
call-to-action
, you place it in a page, and then you fetch the page, you will get a huge page object, which can contain several blocks.
When you want to render blocks, then you will use that
RenderBlocks
component.
RenderBlocks
goes through the array of blocks you pass to the component.
If a
call-to-action
block is currently being in the loop, it will do
components[block.blockType]
eg.
components["call-to-action"]
which is going to find the
call-to-action
key inside
components
which will output the
CallToAction
component
Thanks a lot @Marťafiixek for the great explanation. I get it now.
I'm unable to render my component - the page (coverblocktest) is displaying the default boilerplate text and image and isn't rendering the rich text and styling for the image to be full-screen. Do you know why?
Here's the repo:
I cloned your repository and installed all packages needed, however there is a lot of errors in your source code
And I don't have much time to fix them
I would maybe clone the template again and start again
Because for example, you have these imports
But CoverH2/Component doesn't contain a named export
It only contains a default export
Thanks @Marťafiixek , I'll do that.
Star
Discord
online
Get help straight from the Payload team with an Enterprise License.