Features

Pages (CMS)

CMS-like page management with block-based editing and custom domains

Pages (CMS)

Status: Stable ✅

The Pages feature provides a CMS-like page management system with page types, block-based editing, tree organization, and custom domain support.

Enable

Add the pages and editor layers to your nuxt.config.ts:

// nuxt.config.ts
export default defineNuxtConfig({
  extends: [
    '@fyit/crouton',
    '@fyit/crouton-editor'  // Required for rich text blocks
  ]
})

Key Features

  • Page Types: Regular pages, app-specific pages (booking calendar, product list)
  • Block Editor: Hero sections, feature grids, CTAs, cards, rich text
  • Tree Organization: Hierarchical page ordering with drag-and-drop
  • Custom Domains: Automatic team resolution from custom domains
  • Public Routes: /[team]/[locale]/[...slug] for public pages

URL Structure

RoutePurpose
/[team]/[...slug]Public page display (no locale)
/[team]/[locale]/[...slug]Public page display (with locale)
/[team]/Homepage (empty slug)
/admin/[team]/pagesAdmin page management

Page Types

Apps can register custom page types that appear in the page type selector.

Built-in Types

  • core:regular - Standard rich text page

Registering Custom Types

// app.config.ts
export default defineAppConfig({
  croutonApps: {
    bookings: {
      id: 'bookings',
      name: 'Bookings',
      pageTypes: [
        {
          id: 'calendar',
          name: 'Booking Calendar',
          component: 'CroutonBookingsCalendar',
          icon: 'i-lucide-calendar',
          category: 'customer',
          description: 'Interactive calendar for bookings'
        }
      ]
    }
  }
})

Using Page Types

const {
  pageTypes,           // All aggregated page types (apps + publishable collections)
  getPageType          // Get by fullId (e.g., 'bookings:calendar')
} = usePageTypes()

Block Editor

The block-based editor uses TipTap and Nuxt UI page components.

Block Types

BlockComponentPurpose
heroBlockUPageHeroTitle, description, CTA, image
sectionBlockUPageSectionFeature grid with icons
ctaBlockUPageCTACall-to-action banner
cardGridBlockUPageGrid + UPageCardGrid of cards
separatorBlockUSeparatorVisual divider
richTextBlockprose divStandard text content
faqBlockFAQFrequently asked questions accordion
twoColumnBlockTwoColumnTwo-column layout
imageBlockImageImage with caption
videoBlockVideoEmbedded video
fileBlockFileFile download/attachment
embedBlockEmbedExternal embed (iframe)
buttonRowBlockButtonRowRow of action buttons
statsBlockStatsStatistics/metrics display
galleryBlockGalleryImage gallery grid
logoBlockLogoLogo cloud/display
collectionBlockCollectionDynamic collection listing
addonBlockAddonAddon/extension content

Using the Editor

<template>
  <CroutonPagesBlockContent
    v-model="content"
    placeholder="Type / to insert a block..."
  />
</template>

Content Format

Content auto-detects format:

  • JSON with type: 'doc' - Renders as blocks
  • HTML string - Renders as legacy content
  • Empty - Shows empty state
interface PageBlockContent {
  type: 'doc'
  content: PageBlock[]
}

interface PageBlock {
  type: 'heroBlock' | 'sectionBlock' | 'ctaBlock' | 'cardGridBlock' | 'separatorBlock' | 'richTextBlock' | 'faqBlock' | 'twoColumnBlock' | 'imageBlock' | 'videoBlock' | 'fileBlock' | 'embedBlock' | 'buttonRowBlock' | 'statsBlock' | 'galleryBlock' | 'logoBlock' | 'collectionBlock' | 'addonBlock'
  attrs: Record<string, any>
}

Page Schema

interface PageRecord {
  id: string
  teamId: string
  title: string
  slug: string
  pageType: string        // 'core:regular' or 'appId:pageTypeId'
  content?: string        // For regular pages (JSON blocks or HTML)
  config?: object         // For app pages (type-specific settings)
  status: 'draft' | 'published' | 'archived'
  visibility: 'public' | 'members' | 'hidden'
  showInNavigation: boolean
  parentId?: string       // For hierarchy
  order: number           // For sorting
  path?: string           // Materialized path
  depth?: number          // Nesting level
}

Custom Domains

The pages package supports custom domain resolution.

How It Works

Custom Domain Request: booking.acme.com/about
    │
    ▼
Domain resolver middleware
    │ looks up 'booking.acme.com' in domain table
    │ finds: organizationId → org with slug 'acme'
    ▼
URL Rewrite: /about → /acme/about
    │
    ▼
Normal routing: [team]/[...slug].vue

Domain Context

const {
  isCustomDomain,    // Whether request is from custom domain
  resolvedDomain,    // The custom domain hostname
  resolvedTeamId,    // Team ID from domain lookup
  hideTeamInUrl,     // true on custom domains
  hostname,          // Current hostname
  isAppDomain        // Whether hostname is known app domain
} = useDomainContext()

Configuration

runtimeConfig: {
  public: {
    croutonPages: {
      // Domains to skip (not custom domains)
      appDomains: ['myapp.com', 'staging.myapp.com'],
      debug: false
    }
  }
}

Build navigation from published pages:

const {
  navigation,        // Hierarchical navigation tree
  flatNavigation,    // Flat list of all nav items
  isLoading,         // Loading state
  currentPage,       // Current active page
  isActive,          // Check if nav item is active
  refresh,           // Refresh navigation data
  team               // Current team slug
} = useNavigation()

Components

ComponentPurpose
CroutonPagesRendererRenders page based on type
CroutonPagesRegularContentRich text content display
CroutonPagesBlockContentBlock-based content display and editor
CroutonPagesFormPage creation/editing form (registered via manifest, not shipped as a standalone file)

Admin Page List

<template>
  <CroutonCollection
    collection="pagesPages"
    layout="tree"
    :columns="['title', 'slug', 'pageType', 'status']"
  />
</template>

Rendering Pages

<template>
  <CroutonPagesRenderer :page="pageData" />
</template>

API Endpoints

EndpointMethodPurpose
/api/teams/[id]/pagesGETList published pages (for navigation)
/api/teams/[id]/pages/[...slug]GETGet single page by slug (catch-all)

Use Cases

  • Marketing sites: Landing pages, about, contact
  • Documentation: Knowledge base, help center
  • Portals: Customer dashboards, member areas
  • Multi-tenant apps: Team-specific pages with custom domains