Dyrected
GuidesNext.js

Let Editors Build Custom Pages in Next.js with Dyrected

Set up Dyrected so your marketing & content team or clients can build new landing pages from approved reusable sections without waiting on a developer for every copy and layout change.

Use this page after Adding a Visual Editor in Next.js. This is the page where schema, rendering, and visual preview finally become one complete workflow for editors. At this point, the important pieces should already exist:

  • the pages collection exists
  • the frontend can render Dyrected content
  • preview can open the real route and reflect edits

Now the goal changes. Instead of proving the integration works, you are deciding whether the setup is ready for editors to build real campaign and landing pages without developer help for every change.

By the end, you should know what a safe custom-page workflow looks like, which parts must stay controlled by code, and how the schema, rendering, and preview pieces fit together into one editor-ready system.

Before this page:

  • editors can preview and click into content on a real route
  • the frontend can render Dyrected content correctly

After this page:

  • editors can assemble a new landing page from approved sections
  • preview still shows the real route while they work
  • the frontend keeps the design system and layout guardrails

This guide assumes you already have some real landing-page sections in your frontend. The missing job is turning those existing sections into reusable Dyrected blocks one by one.

Start with the right mental model

For custom landing pages, the goal is usually not "let editors design anything." The goal is "let editors assemble approved page sections fast."

That means the safest setup is:

  • the frontend keeps the design system
  • the schema defines the approved section types
  • editors choose the order and content inside those sections

In Dyrected, that setup is usually:

  • a pages collection with a layout blocks field
  • a Next.js renderer that maps each blockType to a real component
  • a preview setup that opens the real page while editors are working

Choose the sections editors should actually control

Before you write any code, list the section types the team needs most often.

A strong first set is usually small:

  • hero
  • feature grid
  • testimonial
  • call to action

If a section is not already designed in the frontend, do not add it yet. The fastest dashboard is the one that only offers sections the site already knows how to render well.

Start from existing frontend components

Do not design the block schema in a vacuum. Pick one existing section from the site and treat that component as the source of truth for the first block.

For each section you want editors to use:

  1. find the real React component that already renders that section
  2. list the props or content inputs it already expects
  3. separate true content fields from styling and layout internals
  4. keep only the content fields editors should control

For example, an existing hero section might already look roughly like this:

type HeroSectionProps = {
  heading: string;
  subheading?: string;
  ctaLabel?: string;
  ctaUrl?: string;
  image?: Media;
};

export function HeroSection({
  heading,
  subheading,
  ctaLabel,
  ctaUrl,
  image,
}: HeroSectionProps) {
  return (
    <section>
      <h1>{heading}</h1>
      {subheading ? <p>{subheading}</p> : null}
      {ctaLabel && ctaUrl ? <a href={ctaUrl}>{ctaLabel}</a> : null}
      {image ? <DyrectedImage media={image} alt={heading} /> : null}
    </section>
  );
}

That is a good first block candidate because the editable inputs are already clear. The component already owns the layout and styling, so the block only needs to expose the content fields.

Turn one component into one reusable block

Once you know which existing section you are modeling, create a block that matches the content inputs that component really needs.

The safest quick-start rule is:

  • one visible section type equals one block
  • one block should map cleanly to one existing frontend component
  • block fields should describe content, not implementation details

For the hero example above, the block can look like this:

const HeroBlock = defineBlock({
  slug: "hero",
  labels: { singular: "Hero", plural: "Heroes" },
  fields: [
    defineTextField({ name: "heading", label: "Heading", required: true }),
    defineTextareaField({ name: "subheading", label: "Subheading" }),
    defineTextField({ name: "ctaLabel", label: "CTA label" }),
    defineUrlField({ name: "ctaUrl", label: "CTA URL" }),
    defineRelationshipField({
      name: "image",
      label: "Image",
      relationTo: "media",
    }),
  ],
});

That mapping should feel boring on purpose. If the component already works, the block should usually mirror its content inputs instead of inventing a new API.

Add a layout blocks field to the pages collection

After you have converted the first few existing sections into blocks, add them to a layout field on the pages collection. This is the key schema change that turns a basic page model into a controlled landing-page builder.

import {
  defineBlock,
  defineBlocksField,
  defineCollection,
  defineRelationshipField,
  defineRichTextField,
  defineTextField,
  defineTextareaField,
  defineUrlField,
} from "@dyrected/core";

const HeroBlock = defineBlock({
  slug: "hero",
  labels: { singular: "Hero", plural: "Heroes" },
  fields: [
    defineTextField({ name: "heading", label: "Heading", required: true }),
    defineTextareaField({ name: "subheading", label: "Subheading" }),
    defineTextField({ name: "ctaLabel", label: "CTA label" }),
    defineUrlField({ name: "ctaUrl", label: "CTA URL" }),
    defineRelationshipField({
      name: "image",
      label: "Image",
      relationTo: "media",
    }),
  ],
});

const FeatureGridBlock = defineBlock({
  slug: "featureGrid",
  labels: { singular: "Feature grid", plural: "Feature grids" },
  fields: [
    defineTextField({ name: "heading", label: "Heading", required: true }),
    defineRichTextField({ name: "intro", label: "Intro" }),
  ],
});

const TestimonialBlock = defineBlock({
  slug: "testimonial",
  labels: { singular: "Testimonial", plural: "Testimonials" },
  fields: [
    defineTextField({ name: "quote", label: "Quote", required: true }),
    defineTextField({ name: "name", label: "Name", required: true }),
    defineTextField({ name: "role", label: "Role" }),
  ],
});

const CallToActionBlock = defineBlock({
  slug: "callToAction",
  labels: { singular: "Call to action", plural: "Calls to action" },
  fields: [
    defineTextField({ name: "heading", label: "Heading", required: true }),
    defineTextareaField({ name: "body", label: "Body" }),
    defineTextField({ name: "label", label: "Button label", required: true }),
    defineUrlField({ name: "url", label: "Button URL", required: true }),
  ],
});

export const Pages = defineCollection({
  slug: "pages",
  labels: { singular: "Page", plural: "Pages" },
  admin: {
    useAsTitle: "title",
    previewUrl: "slug == 'home' ? '/' : '/' + slug",
    urlPattern: "/{slug}",
  },
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineTextField({ name: "slug", label: "Slug", required: true }),
    defineBlocksField({
      name: "layout",
      label: "Page layout",
      blocks: [HeroBlock, FeatureGridBlock, TestimonialBlock, CallToActionBlock],
    }),
  ],
});

This keeps the page structure flexible, but only inside the set of blocks you approved.

Make sure the frontend can render every approved section

The dashboard is only half of the workflow. Every approved block also needs a matching Next.js component in the frontend.

That means:

  • every blockType in the schema must map to a real component
  • the page route must render the layout field in order
  • preview must open the same route editors are trying to build

If one of those pieces is missing, this stops being a custom-page workflow and turns back into a schema exercise.

In practice, the simplest path is usually to keep your existing section component and add a thin adapter only if the prop shape needs cleanup.

For example, a route can render the approved blocks like this:

import { Blocks } from "@dyrected/next";
import { HeroSection } from "@/components/marketing/HeroSection";
import { FeatureGridSection } from "@/components/marketing/FeatureGridSection";
import { TestimonialSection } from "@/components/marketing/TestimonialSection";
import { CallToActionSection } from "@/components/marketing/CallToActionSection";

function HeroBlockView(props: {
  heading: string;
  subheading?: string;
  ctaLabel?: string;
  ctaUrl?: string;
  image?: Media;
}) {
  return <HeroSection {...props} />;
}

<Blocks
  items={page.layout}
  path="layout"
  components={{
    hero: HeroBlockView,
    featureGrid: FeatureGridSection,
    testimonial: TestimonialSection,
    callToAction: CallToActionSection,
  }}
/>;

If you need the shared rendering pattern first, go back to Displaying Content in Next.js, especially the section on rendering blocks.

Repeat that conversion for each approved section

At this point, the workflow for every new landing-page section is the same:

  1. start from an existing frontend component
  2. identify the content inputs editors should control
  3. define a matching Dyrected block
  4. add that block to the layout field
  5. register the matching component in the frontend Blocks mapping
  6. confirm the section renders and previews correctly

That repetition is normal. A landing-page builder is not one magic switch. It is a small library of approved section components that you wire into the schema and renderer one by one.

Keep the page builder controlled

This is where the setup either stays useful or becomes messy.

For a quick-start editorial workflow:

  • keep global site chrome like navigation and settings outside the page layout
  • keep one block focused on one visible section type
  • keep field names content-focused, not implementation-focused
  • avoid adding arbitrary JSON fields or open-ended style controls

A good page builder helps marketing move fast inside guardrails. It should not ask them to invent layout rules the frontend does not already understand.

Build one real landing page in the dashboard

Once schema, rendering, and preview all exist, create one real landing page in the admin and add a few sections in order:

  1. a hero
  2. a feature grid
  3. a testimonial
  4. a call to action

Then open that page in preview and make one real editorial change: update the hero copy, reorder a section, or replace a call to action.

That is the moment this guide is aiming for. It proves editors are no longer just editing fields. They are building a real page inside the system you prepared for them.

Know what this page depends on

This workflow only works because the earlier quick-start pages already handled the pieces below:

This page is where those pieces finally become one editor-ready landing-page workflow.

What this setup gives editors

Once this is in place, the dashboard can support the tasks marketing teams usually care about most:

  • create a new landing page
  • reorder approved page sections
  • update hero copy, proof, and calls to action
  • publish a campaign page without asking a developer for every text edit

What it does not do is allow arbitrary new section types or arbitrary design changes. That stays with the product or frontend team, which is usually the safer split.

Where to go deeper

Use this page for the quick-start version of the pattern. For the full background, continue to:

Success check

You are ready for the next step when:

  • the pages collection has a layout blocks field
  • the allowed block types match real designed page sections
  • the frontend renders those sections correctly
  • preview reflects changes on a real page
  • the dashboard can create at least one landing page from those approved sections

Once that is true, editors can create a page, add approved sections, reorder them, preview the result on the real route, and publish routine content changes without developer help. When you are ready to give access to another person, continue to Handing Off to Editors.

On this page

Dyrected| Cloud

Get your backend ready in minutes

Use a managed database, storage, APIs, and admin dashboard without setting up the infrastructure yourself.

Set Up My Backend