Appearance
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 contentContent 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
- Tone: Professional yet approachable
- Voice: Helpful, knowledgeable, empowering
- 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)
- Edit Content: Modify TSX/TS files directly
- Preview: Local development server
- Review: Pull request process
- Deploy: Automatic via CI/CD
Blog Content (Sanity CMS)
- Edit Content: Use Sanity Studio at
/studio - Preview: Content updates automatically in development
- 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
- Version Control: Content versioning and rollback
- Preview Mode: Live preview before publishing
- Workflow: Draft, review, publish states
- Media Management: Image/video handling
- SEO Tools: Meta tag management
- Multi-language: i18n support
Recommended Platforms
- Contentful: Enterprise-grade, flexible
- Strapi: Open-source, self-hosted option
- Sanity: Real-time collaboration
- 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
}
}