Skip to content

Content Management

Overview

The Jubiloop marketing site now uses Sanity as a headless CMS for the blog (posts, authors, categories) while keeping general marketing copy code-driven. This document outlines the content structure and CMS integration strategy.

Content Architecture

Static Content (Code-Driven)

Marketing copy and static content is organized in:

src/
├── components/          # Component-embedded content
│   ├── home/           # Homepage section content
│   ├── contact/        # Contact page content
│   └── ...
├── utils/
│   ├── strings.ts      # Centralized text strings
│   └── metadata.ts     # SEO and meta content
└── app/                # Page-level content
    └── (main)/
        └── */page.tsx  # Page-specific content

Content Types

1. Marketing Copy

Homepage hero section example:

typescript
// src/components/home/HeroSection.tsx
const heroContent = {
  headline: 'Plan Memorable Events with AI-Powered Assistance',
  subheadline: 'From intimate gatherings to grand celebrations',
  cta: {
    primary: 'Start Planning',
    secondary: 'Watch Demo',
  },
}

2. Feature Descriptions

Centralized feature data:

typescript
// src/components/home/features/featuresData.ts
export const features = [
  {
    id: 'ai-suggestions',
    title: 'AI-Powered Suggestions',
    description: 'Get intelligent vendor recommendations...',
    icon: 'brain',
    animation: 'AISuggestionsAnimation',
  },
  // ... more features
]

3. FAQ Content

Structured FAQ data:

typescript
// src/utils/faqData.ts
export const faqCategories = [
  {
    category: 'General',
    questions: [
      {
        question: 'What is Jubiloop?',
        answer: 'Jubiloop is an AI-powered event planning platform...',
      },
    ],
  },
]

4. SEO Metadata

Centralized metadata management:

typescript
// src/utils/metadata.ts
export const siteMetadata = {
  title: 'Jubiloop - AI Event Planning Platform',
  description: 'Plan memorable events with AI assistance',
  keywords: ['event planning', 'AI', 'vendors'],
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://jubiloop.com',
    siteName: 'Jubiloop',
  },
}

Content Management Patterns

Component-Level Content

For tightly coupled UI/content:

typescript
export function FeatureCard() {
  return (
    <div className="feature-card">
      <h3>Smart Vendor Matching</h3>
      <p>Our AI analyzes your requirements to find perfect vendors</p>
    </div>
  )
}

Centralized Strings

For reusable text across components:

typescript
// src/utils/strings.ts
export const strings = {
  common: {
    learnMore: 'Learn More',
    getStarted: 'Get Started',
    contactUs: 'Contact Us'
  },
  hero: {
    title: 'Plan Memorable Events',
    subtitle: 'With AI-Powered Assistance'
  }
}

// Usage
import { strings } from '@/utils/strings'
<h1>{strings.hero.title}</h1>

Dynamic Content (Sanity CMS)

Blog content is managed through Sanity CMS:

typescript
// Blog content loading
import { getPostResource, getAllPostsResource } from '@/utils/blog/posts'
import { getAuthorResource } from '@/utils/blog/authors'
import { getCategoryResource } from '@/utils/blog/categories'

// Server-side data fetching
export default async function BlogPage() {
  const { data: posts, pagination } = await getAllPostsResource({
    page: 1,
    limit: 6,
    search: '',
    category: 'all'
  })

  return <BlogPostsGrid posts={posts} />
}

Content Types:

  • Posts: Blog articles with rich text content
  • Authors: Author profiles with bios and specialties
  • Categories: Content categorization and filtering

Page Content Structure

Homepage Content

typescript
// src/app/(main)/page.tsx
import { HeroSection } from '@/components/home/HeroSection'
import { FeaturesSection } from '@/components/home/FeaturesSection'
import { EventTypesSection } from '@/components/home/EventTypesSection'
import { CTASection } from '@/components/home/CTASection'

export default function HomePage() {
  return (
    <>
      <HeroSection />
      <FeaturesSection />
      <EventTypesSection />
      <CTASection />
    </>
  )
}

Contact Page Content

typescript
// src/components/contact/ContactInfo.tsx
const contactInfo = {
  email: 'hello@jubiloop.com',
  phone: '+1 (555) 123-4567',
  address: {
    street: '123 Innovation Drive',
    city: 'San Francisco',
    state: 'CA',
    zip: '94105',
  },
  hours: {
    weekdays: '9:00 AM - 6:00 PM PST',
    weekends: 'Closed',
  },
}

Content Guidelines

Writing Style

  1. Tone: Professional yet approachable
  2. Voice: Helpful, knowledgeable, empowering
  3. Language: Clear, jargon-free, action-oriented

Content Principles

typescript
// Example of good content structure
const featureContent = {
  // Clear, benefit-focused headline
  headline: 'Save Hours on Vendor Research',

  // Specific, value-driven description
  description:
    'Our AI analyzes thousands of vendors to find the perfect matches for your event, budget, and style.',

  // Action-oriented CTA
  cta: 'Start Your Search',

  // Supporting details
  benefits: ['Verified vendor ratings', 'Price transparency', 'Instant availability'],
}

SEO Best Practices

typescript
// Page metadata example
export const metadata: Metadata = {
  title: 'Contact Us | Jubiloop Event Planning',
  description:
    'Get in touch with Jubiloop for event planning assistance. Available Monday-Friday, 9AM-6PM PST.',
  keywords: ['contact jubiloop', 'event planning help', 'customer support'],
  alternates: {
    canonical: 'https://jubiloop.com/contact',
  },
}

Internationalization (i18n)

Future Implementation

typescript
// src/lib/i18n.ts
export const translations = {
  en: {
    hero: {
      title: 'Plan Memorable Events',
      subtitle: 'With AI-Powered Assistance'
    }
  },
  es: {
    hero: {
      title: 'Planifica Eventos Memorables',
      subtitle: 'Con Asistencia de IA'
    }
  }
}

// Usage with future i18n hook
const { t } = useTranslation()
<h1>{t('hero.title')}</h1>

Content Versioning

A/B Testing Content

typescript
// src/lib/experiments.ts
export function getHeroContent(variant: 'A' | 'B') {
  const variants = {
    A: {
      headline: 'Plan Your Perfect Event',
      cta: 'Get Started',
    },
    B: {
      headline: 'Create Unforgettable Moments',
      cta: 'Start Planning',
    },
  }

  return variants[variant]
}

Dynamic Content Integration

Newsletter Signup

typescript
// src/components/newsletter/NewsletterForm.tsx
async function handleSubmit(email: string) {
  // Integration with email service
  await fetch('/api/newsletter', {
    method: 'POST',
    body: JSON.stringify({ email }),
    headers: { 'Content-Type': 'application/json' },
  })
}

Blog Integration (Implemented)

typescript
// Blog utilities with Sanity integration
import { getPostResource, getAllPostsResource } from '@/utils/blog/posts'
import { getAuthorResource, getAllAuthorsResource } from '@/utils/blog/authors'
import { getCategoryResource, getAllCategoriesResource } from '@/utils/blog/categories'

// Server-side pagination
const { posts, pagination } = await getAllPostsResource({
  page: 1,
  limit: 6,
  search: 'query',
  category: 'wedding',
})

Features:

  • Server-side pagination and search
  • Author profiles with bio extraction
  • Category filtering and related content
  • Responsive grid layouts

Content Update Workflow

Static Content (Code-Driven)

  1. Edit Content: Modify TSX/TS files directly
  2. Preview: Local development server
  3. Review: Pull request process
  4. Deploy: Automatic via CI/CD

Blog Content (Sanity CMS)

  1. Edit Content: Use Sanity Studio at /studio
  2. Preview: Content updates automatically in development
  3. Deploy: Content goes live with next deployment
typescript
// Example CMS integration
import { createClient } from '@/lib/cms'

export async function getPageContent(slug: string) {
  const cms = createClient()

  const content = await cms.getEntry({
    content_type: 'page',
    'fields.slug': slug,
  })

  return {
    title: content.fields.title,
    sections: content.fields.sections,
    metadata: content.fields.metadata,
  }
}

Performance Considerations

Static Generation

typescript
// Pre-render content at build time
export async function generateStaticParams() {
  const pages = await getContentPages()

  return pages.map((page) => ({
    slug: page.slug,
  }))
}

Content Caching

typescript
// Cache dynamic content
import { unstable_cache } from 'next/cache'

export const getCachedContent = unstable_cache(
  async (slug: string) => {
    return await fetchContentFromCMS(slug)
  },
  ['content'],
  {
    revalidate: 3600, // 1 hour
    tags: ['content'],
  }
)

Content Security

Sanitization

typescript
import DOMPurify from 'isomorphic-dompurify'

export function sanitizeContent(html: string) {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a'],
    ALLOWED_ATTR: ['href', 'target', 'rel'],
  })
}

Content Validation

typescript
import { z } from 'zod'

const ContentSchema = z.object({
  title: z.string().min(1).max(200),
  description: z.string().min(1).max(500),
  body: z.string(),
  publishedAt: z.date(),
  author: z.string(),
})

export function validateContent(content: unknown) {
  return ContentSchema.parse(content)
}

Future CMS Requirements

Essential Features

  1. Version Control: Content versioning and rollback
  2. Preview Mode: Live preview before publishing
  3. Workflow: Draft, review, publish states
  4. Media Management: Image/video handling
  5. SEO Tools: Meta tag management
  6. Multi-language: i18n support
  1. Contentful: Enterprise-grade, flexible
  2. Strapi: Open-source, self-hosted option
  3. Sanity: Real-time collaboration
  4. Prismic: Developer-friendly

Integration Architecture

typescript
// src/lib/cms/client.ts
export class CMSClient {
  constructor(private config: CMSConfig) {}

  async getContent(query: ContentQuery) {
    // Fetch from CMS API
  }

  async updateContent(id: string, data: ContentUpdate) {
    // Update via CMS API
  }
}

Built with ❤️ by the Jubiloop team