Guide: Blog Integration via Data Mode

πŸ€– Using Cursor? Install the PageGun rules for the best experience: Cursor Rules

Build a high-performance blog powered by PageGun. Use the API to manage articles, and the public CDN to serve them β€” no rate limits, instant global delivery.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      WRITE PATH (API)                       β”‚
β”‚                                                             β”‚
β”‚   Your CMS / Script / AI Agent                              β”‚
β”‚         β”‚                                                   β”‚
β”‚         β–Ό                                                   β”‚
β”‚   POST /pages  ──▢  Create article (markdown)               β”‚
β”‚   PUT /pages/:id ──▢  Update article                        β”‚
β”‚   POST /pages/:id/publish ──▢  Encrypt + push to CDN       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      READ PATH (CDN)                        β”‚
β”‚                                                             β”‚
β”‚   content.pagegun.com/{project_id}/nav.enc                  β”‚
β”‚       └── Article index (titles, slugs, metadata)           β”‚
β”‚                                                             β”‚
β”‚   content.pagegun.com/{project_id}/{subroute}/{slug}.enc    β”‚
β”‚       └── Full article content (encrypted)                  β”‚
β”‚                                                             β”‚
β”‚   Your App (Next.js / Nuxt / etc.)                          β”‚
β”‚       └── Fetch .enc β†’ Decrypt server-side β†’ Render         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why this architecture?

API (write)CDN (read)
Rate limit100 req/minUnlimited
Latency~200ms~20ms (global edge)
Auth requiredYes (API key)No (public, encrypted)
CostFree tierFree (Cloudflare R2)

Prerequisites

  • A PageGun account with an API key (pgk_live_...)
  • Your project ID and Content Key (found in your Dashboard under Settings)
  • Node.js 18+ (for decryption examples)

Step 1: Get Your API Key

Get your API key from the PageGun Dashboard under Settings β†’ API Keys.

export PAGEGUN_API_KEY="pgk_live_your_key_here" export PROJECT_ID="your_project_id"

Step 2: Get Your Content Key (Encryption Key)

The Content Key is used to decrypt content fetched from the CDN. You can find it in your Dashboard under Settings β†’ Data Mode.

export PAGEGUN_CONTENT_KEY="your_content_key_hex"

⚠️ Keep your Content Key secret. Store it as a server-side environment variable β€” never expose it in client-side code.

Step 3: Create Blog Articles

curl -X POST "https://api.pagegun.com/pages" \ -H "Authorization: Bearer $PAGEGUN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "How to Use Our Platform", "slug": "how-to-use-our-platform", "subroute": "blog", "type": "article", "project_id": "'"$PROJECT_ID"'", "description": "A step-by-step guide to getting started with our platform.", "og_image_url": "https://cdn.example.com/blog/how-to-use-og.jpg", "markdown_content": "# How to Use Our Platform\n\nWelcome! In this guide we will walk you through...\n\n## Getting Started\n\nFirst, sign up for an account...\n\n## Key Features\n\n- **Dashboard** β€” manage everything in one place\n- **API access** β€” automate your workflow\n- **Analytics** β€” track performance\n\n## Next Steps\n\nCheck out our [API docs](/docs) for more details." }'

Response:

{ "data": { "id": "page_abc123", "title": "How to Use Our Platform", "slug": "how-to-use-our-platform", "subroute": "blog", "type": "article", "status": "draft" } }

Article Fields Reference

FieldRequiredDescription
titleYesArticle title (H1 + page title)
slugYesURL path segment (lowercase, hyphens)
subrouteYesURL prefix, e.g. "blog" β†’ /blog/slug
typeYesAlways "article"
project_idYesYour project ID
descriptionNoSEO meta description (155 chars recommended)
og_image_urlNoSocial sharing image (1200Γ—630px)
markdown_contentYesFull article body in Markdown

πŸ“ Need author support? The author field for articles is coming soon. If you need author attribution now, please contact the developer.

Supported Markdown

  • Headings (h1–h6)
  • Bold, italic, strikethrough
  • Ordered and unordered lists
  • Links and images
  • Code blocks with syntax highlighting
  • Tables
  • Blockquotes

Adding Images

Images use standard Markdown syntax with external URLs. Host your images on any CDN or storage service (S3, Cloudflare R2, Cloudinary, Imgur, etc.):

# Inline image ![Dashboard overview](https://cdn.example.com/blog/dashboard-overview.png) # Image with link [![Feature screenshot](https://cdn.example.com/blog/feature.png)](https://example.com/features)

In the API request, include images directly in markdown_content:

curl -X POST "https://api.pagegun.com/pages" \ -H "Authorization: Bearer $PAGEGUN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Product Update: New Dashboard", "slug": "new-dashboard", "subroute": "blog", "type": "article", "project_id": "'"$PROJECT_ID"'", "og_image_url": "https://cdn.example.com/blog/new-dashboard-og.jpg", "markdown_content": "# Product Update: New Dashboard\n\nWe are excited to announce our redesigned dashboard.\n\n![New dashboard](https://cdn.example.com/blog/new-dashboard.png)\n\n## Key Changes\n\n- Faster navigation\n- Real-time analytics\n\n![Analytics panel](https://cdn.example.com/blog/analytics-panel.png)" }'

Hosting Images on PageGun CDN

You can also upload images directly to PageGun's CDN using the Media API, instead of hosting them yourself.

Method 1: Upload from URL

curl -X POST "https://api.pagegun.com/media" \ -H "Authorization: Bearer $PAGEGUN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/photo.jpg", "project_id": "'"$PROJECT_ID"'", "alt_text": "Dashboard overview screenshot", "label": "dashboard-overview" }'

Method 2: Upload file directly

curl -X POST "https://api.pagegun.com/media" \ -H "Authorization: Bearer $PAGEGUN_API_KEY" \ -F "file=@./dashboard.png" \ -F "project_id=$PROJECT_ID" \ -F "alt_text=Dashboard overview screenshot"

The response includes a hosted url β€” use it in your markdown content:

{ "data": { "url": "https://content.pagegun.com/YOUR_PROJECT_ID/media/dashboard-overview.png", "alt_text": "Dashboard overview screenshot" } }

Then reference it in your article:

![Dashboard overview](https://content.pagegun.com/YOUR_PROJECT_ID/media/dashboard-overview.png)

Tips:

  • Set og_image_url for social sharing previews (1200Γ—630px recommended)
  • Use descriptive alt text for SEO and accessibility
  • Optimize image file size before uploading
  • Use label to give images readable names on the CDN

Step 4: Publish to CDN

Publishing encrypts the article and pushes it to the global CDN:

curl -X POST "https://api.pagegun.com/pages/page_abc123/publish" \ -H "Authorization: Bearer $PAGEGUN_API_KEY"

After publishing, the article is available at:

https://content.pagegun.com/{project_id}/blog/how-to-use-our-platform.enc

The navigation index is also updated:

https://content.pagegun.com/{project_id}/nav.enc

Step 5: Fetch and Decrypt in Your App

Decryption Utility (Node.js / TypeScript)

// lib/pagegun.ts import crypto from 'crypto'; const PROJECT_ID = process.env.PAGEGUN_PROJECT_ID!; const CONTENT_KEY = process.env.PAGEGUN_CONTENT_KEY!; const CDN_BASE = `https://content.pagegun.com/${PROJECT_ID}`; interface PageData { title: string; slug: string; subroute: string; description: string; og_image_url: string | null; markdown_content: string; published_at: string; } interface NavItem { title: string; slug: string; subroute: string; description: string; og_image_url: string | null; published_at: string; } function decrypt(encrypted: string): string { const [ivHex, authTagHex, ciphertext] = encrypted.split(':'); const iv = Buffer.from(ivHex, 'hex'); const authTag = Buffer.from(authTagHex, 'hex'); const key = Buffer.from(CONTENT_KEY, 'hex'); const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(ciphertext, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } /** * Fetch the navigation index (all published pages). * Filter by subroute to get blog articles only. */ export async function getBlogArticles(): Promise<NavItem[]> { const res = await fetch(`${CDN_BASE}/nav.enc`); if (!res.ok) return []; const encrypted = await res.text(); const nav = JSON.parse(decrypt(encrypted)); return nav.items .filter((item: NavItem) => item.subroute === 'blog') .sort((a: NavItem, b: NavItem) => new Date(b.published_at).getTime() - new Date(a.published_at).getTime() ); } /** * Fetch a single blog article by slug. */ export async function getBlogArticle(slug: string): Promise<PageData | null> { const res = await fetch(`${CDN_BASE}/blog/${slug}.enc`); if (!res.ok) return null; const encrypted = await res.text(); return JSON.parse(decrypt(encrypted)); }

Next.js App Router Example

// app/blog/page.tsx β€” Blog listing import { getBlogArticles } from '@/lib/pagegun'; import Link from 'next/link'; export default async function BlogPage() { const articles = await getBlogArticles(); return ( <div> <h1>Blog</h1> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> {articles.map((article) => ( <Link key={article.slug} href={`/blog/${article.slug}`}> <article className="border rounded-lg p-4 hover:shadow-lg transition"> {article.og_image_url && ( <img src={article.og_image_url} alt={article.title} className="rounded mb-3" /> )} <h2 className="text-xl font-bold">{article.title}</h2> <p className="text-gray-600 mt-2">{article.description}</p> <time className="text-sm text-gray-400 mt-2 block"> {new Date(article.published_at).toLocaleDateString()} </time> </article> </Link> ))} </div> </div> ); }
// app/blog/[slug]/page.tsx β€” Single article import { getBlogArticle, getBlogArticles } from '@/lib/pagegun'; import { notFound } from 'next/navigation'; import ReactMarkdown from 'react-markdown'; // Generate static paths at build time export async function generateStaticParams() { const articles = await getBlogArticles(); return articles.map((a) => ({ slug: a.slug })); } // SEO metadata export async function generateMetadata({ params }: { params: { slug: string } }) { const article = await getBlogArticle(params.slug); if (!article) return {}; return { title: article.title, description: article.description, openGraph: { title: article.title, description: article.description, images: article.og_image_url ? [article.og_image_url] : [], }, }; } export default async function ArticlePage({ params }: { params: { slug: string } }) { const article = await getBlogArticle(params.slug); if (!article) notFound(); return ( <article className="prose lg:prose-xl mx-auto py-10"> <h1>{article.title}</h1> <time className="text-gray-500"> {new Date(article.published_at).toLocaleDateString()} </time> <ReactMarkdown>{article.markdown_content}</ReactMarkdown> </article> ); }

Environment Variables

Add to your .env.local:

PAGEGUN_PROJECT_ID=your_project_id PAGEGUN_CONTENT_KEY=your_content_key_hex

⚠️ Never expose PAGEGUN_CONTENT_KEY to client-side code. Always decrypt server-side (in Server Components, getServerSideProps, or API routes).

Step 6: Update and Re-publish

When you update an article, re-publish to push changes to CDN:

# Update content curl -X PUT "https://api.pagegun.com/pages/page_abc123" \ -H "Authorization: Bearer $PAGEGUN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"markdown_content": "# Updated Title\n\nNew content here..."}' # Re-publish to CDN curl -X POST "https://api.pagegun.com/pages/page_abc123/publish" \ -H "Authorization: Bearer $PAGEGUN_API_KEY"

Changes appear on CDN within seconds.

Caching

CDN content updates immediately after publishing. How you cache and revalidate on your end is entirely up to your hosting setup.

SEO Checklist

  • βœ… Use descriptive, keyword-rich slug values (e.g. how-to-use-our-platform)
  • βœ… Write description under 155 characters with target keyword
  • βœ… Set og_image_url (1200Γ—630px) for social sharing
  • βœ… Structure markdown with proper heading hierarchy (H1 β†’ H2 β†’ H3)
  • βœ… Use generateMetadata in Next.js for dynamic meta tags
  • βœ… Generate a sitemap from nav.enc data

Complete Workflow Summary

1. Create article  β†’  POST /pages (type: "article", markdown_content: "...")
2. Publish          β†’  POST /pages/:id/publish
3. CDN delivers     β†’  content.pagegun.com/{project_id}/blog/{slug}.enc
4. Your app reads   β†’  fetch .enc β†’ decrypt β†’ render markdown
5. Update           β†’  PUT /pages/:id β†’ POST /pages/:id/publish
Β© 2026 PageGun. All rights reserved.