Reaudit Logo
Agencies
AI Rankings
Pricing
Contact
Log in

Footer

500+ Companies
Trust Reaudit
99.9% Uptime
Reliable Service
Global Coverage
Worldwide Support
Reaudit
Enterprise GEO Intelligence Platform

Advanced AI-powered GEO auditing and competitive intelligence for enterprise businesses. Dominate search rankings with data-driven insights.

hello@reaudit.com
+30 697 330 5186
4 Adelfon Giannidi, Moschato, Attica, Greece

Product

  • Optimization Station
  • AI Visibility
  • Content Factory
  • Reporting & Analytics
  • GTM Strategy

Company

  • About Us
  • Pricing
  • Careers
  • Partners
  • Press Kit
  • Contact

Resources

  • Documentation
  • Help Center
  • Blog
  • AEO/GEO Glossary
  • Case Studies
  • Webinars
  • AI Rankings
  • Free Tools

Legal

  • Privacy Policy
  • Terms of Service
  • Security
  • Compliance
  • Cookie Policy

© 2025 Reaudit, Inc. All rights reserved.

Powered by Leadflow.tech
OverviewWhat's NewGetting StartedTools ReferenceUse CasesFAQ
Analytics QuerySmart Filters
React / Next.jsWordPressSite TrackingSocial MediaStripeGoogle Analytics 4

React / Next.js Integration

Publish AI-optimized content to your React or Next.js site via webhooks

When you publish content in Reaudit, the full article is pushed to your site via webhook. Your site stores it locally and serves it from your own domain — giving you full control and maximum AI search visibility.

5 min setup
10+ languages
Signed webhooks
Push-based

Overview

How It Works

1

Publish in Reaudit

Click “Publish to React” in the dashboard. Reaudit sends the full article to your webhook.

2

Your Site Stores It

Your webhook endpoint verifies the signature, saves the article to your local database.

3

Serve on Your Domain

Your site serves content from its own database — AI search engines index YOUR domain, not Reaudit's.

Why webhooks? Content must live on YOUR domain for AI search engines (ChatGPT, Perplexity, Claude) to cite YOU. Reaudit pushes it once, your site owns it from there.

Prerequisites

  • ✓Reaudit Account — Sign up at reaudit.io
  • ✓React Connection — Create a connection in Dashboard → Tools → React/Next.js
  • ✓Webhook URL — A publicly accessible endpoint on your site (e.g., /api/reaudit-webhook)
  • ✓Database — MongoDB, Postgres, or any database to store articles locally
  • ✓React/Next.js Project — Next.js 13+ recommended (App Router)

Step 1: Environment Setup

Create a .env.local file in your project root:

# .env.local
REAUDIT_WEBHOOK_SECRET=your_api_secret_here
Important: The webhook secret is your API Secret from the React connection setup. Add .env.local to your .gitignore.

Where to find your credentials

  1. Go to Reaudit Dashboard
  2. Navigate to Tools → React/Next.js
  3. Click “Connect React Site”
  4. Enter your webhook URL (e.g., https://yoursite.com/api/reaudit-webhook)
  5. Copy your API Secret — this is your webhook signature secret

Step 2: Create Article Model

Create a model to store articles in your local database:

// models/Article.ts
import mongoose, { Schema, Document } from 'mongoose';

export interface IArticle extends Document {
  externalId: string;      // Reaudit content ID
  title: string;
  slug: string;
  content: string;         // Full HTML
  excerpt?: string;
  metaTitle?: string;
  metaDescription?: string;
  author?: string;
  authorCard?: Record<string, any>;
  keywords: string[];
  categories: string[];
  language: string;
  section: string;         // e.g., 'blog', 'news'
  featuredImage?: string;
  schemaMarkup?: string;
  markdownContent?: string; // For AI crawlers
  wordCount?: number;
  publishedAt: Date;
}

const ArticleSchema = new Schema<IArticle>({
  externalId: { type: String, required: true, unique: true },
  title: { type: String, required: true },
  slug: { type: String, required: true, unique: true },
  content: { type: String, required: true },
  excerpt: String,
  metaTitle: String,
  metaDescription: String,
  author: String,
  authorCard: Schema.Types.Mixed,
  keywords: [String],
  categories: [String],
  language: { type: String, default: 'en' },
  section: { type: String, default: 'blog' },
  featuredImage: String,
  schemaMarkup: String,
  markdownContent: String,
  wordCount: Number,
  publishedAt: { type: Date, default: Date.now },
}, { timestamps: true });

export const Article =
  mongoose.models.Article ||
  mongoose.model<IArticle>('Article', ArticleSchema);

Step 3: Webhook Endpoint

Create the endpoint that receives published articles from Reaudit:

// app/api/reaudit-webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
import { connectToDatabase } from '@/lib/db';
import { Article } from '@/models/Article';

function verifySignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

export async function POST(req: NextRequest) {
  try {
    // 1. Verify webhook signature
    const payload = await req.text();
    const signature = req.headers.get('x-webhook-signature');
    const secret = process.env.REAUDIT_WEBHOOK_SECRET!;

    if (!signature || !verifySignature(payload, signature, secret)) {
      return NextResponse.json(
        { error: 'Invalid signature' },
        { status: 401 }
      );
    }

    // 2. Parse — full article is included in the payload
    const webhook = JSON.parse(payload);
    const article = webhook.data;

    // 3. Save to your local database (upsert)
    await connectToDatabase();
    await Article.findOneAndUpdate(
      { externalId: webhook.contentId },
      {
        externalId: webhook.contentId,
        title: article.title,
        slug: article.slug,
        content: article.content,
        excerpt: article.excerpt,
        metaTitle: article.metaTitle,
        metaDescription: article.metaDescription,
        author: article.author,
        authorCard: article.authorCard,
        keywords: article.tags || [],
        categories: article.categories || [],
        language: article.language || 'en',
        section: article.section || 'blog',
        featuredImage: article.featuredImage,
        schemaMarkup: article.schemaMarkup,
        markdownContent: article.markdownContent,
        wordCount: article.wordCount,
        publishedAt: new Date(webhook.timestamp),
      },
      { upsert: true, new: true }
    );

    return NextResponse.json({ success: true });
  } catch (error) {
    console.error('Webhook error:', error);
    return NextResponse.json(
      { error: 'Processing failed' },
      { status: 500 }
    );
  }
}

Webhook Payload

Reaudit sends the complete article in the webhook — no second download call needed:

// Webhook payload — sent automatically by Reaudit
{
  "event": "content.published",
  "contentId": "691c3d31e5034e2c607dfe72",
  "timestamp": "2026-02-12T10:30:00.000Z",
  "data": {
    "title": "Guide: AI Search Optimization",
    "slug": "ai-search-optimization",
    "content": "<h1>...</h1><p>Full HTML...</p>",
    "excerpt": "Brief excerpt...",
    "metaTitle": "AI Search Optimization | Guide",
    "metaDescription": "Learn how to...",
    "author": "Rose Samaras",
    "authorCard": { "name": "...", "bio": "..." },
    "tags": ["seo", "ai-search"],
    "categories": ["guides"],
    "language": "en",
    "section": "blog",
    "featuredImage": "https://...",
    "schemaMarkup": "{\"@context\":\"https://schema.org\"...}",
    "markdownContent": "---\ntitle: ...\n---\n...",
    "wordCount": 2500,
    "seoScore": 85,
    "readabilityScore": 75
  }
}
Signature verification: Every webhook includes an X-Webhook-Signature header — an HMAC SHA-256 of the payload using your API Secret. Always verify this.

Step 4: Blog Article Page

Create app/blog/[slug]/page.tsx — reads from your local database:

// app/blog/[slug]/page.tsx
import { connectToDatabase } from '@/lib/db';
import { Article } from '@/models/Article';
import { notFound } from 'next/navigation';

export async function generateMetadata({ params }) {
  await connectToDatabase();
  const article = await Article.findOne({ slug: params.slug }).lean();
  if (!article) return { title: 'Not Found' };

  return {
    title: article.metaTitle || article.title,
    description: article.metaDescription || article.excerpt,
  };
}

export async function generateStaticParams() {
  await connectToDatabase();
  const articles = await Article.find({ section: 'blog' })
    .select('slug')
    .lean();
  return articles.map((a) => ({ slug: a.slug }));
}

export default async function BlogArticle({ params }) {
  await connectToDatabase();
  const article = await Article.findOne({ slug: params.slug }).lean();
  if (!article) notFound();

  return (
    <main className="max-w-4xl mx-auto px-4 py-12">
      {article.featuredImage && (
        <img
          src={article.featuredImage}
          alt={article.title}
          className="w-full h-auto rounded-lg mb-8"
        />
      )}

      <h1 className="text-4xl font-bold mb-4">{article.title}</h1>

      {article.excerpt && (
        <p className="text-xl text-gray-600 mb-4">{article.excerpt}</p>
      )}

      <div
        className="prose prose-lg max-w-none"
        dangerouslySetInnerHTML={{ __html: article.content }}
      />

      {article.schemaMarkup && (
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: article.schemaMarkup }}
        />
      )}
    </main>
  );
}

Step 5: Article Listing

Create app/blog/page.tsx:

// app/blog/page.tsx
import { connectToDatabase } from '@/lib/db';
import { Article } from '@/models/Article';
import Link from 'next/link';

export default async function BlogListing() {
  await connectToDatabase();
  const articles = await Article.find({ section: 'blog' })
    .sort({ publishedAt: -1 })
    .limit(12)
    .lean();

  return (
    <main className="max-w-7xl mx-auto px-4 py-12">
      <h1 className="text-4xl font-bold mb-8">Blog</h1>

      <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
        {articles.map((article) => (
          <Link
            key={article._id.toString()}
            href={`/blog/${article.slug}`}
            className="group"
          >
            <article className="border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
              {article.featuredImage && (
                <img
                  src={article.featuredImage}
                  alt={article.title}
                  className="w-full h-48 object-cover"
                />
              )}
              <div className="p-6">
                <h2 className="text-xl font-bold mb-2 group-hover:text-blue-600">
                  {article.title}
                </h2>
                <p className="text-gray-600 line-clamp-3">
                  {article.excerpt}
                </p>
              </div>
            </article>
          </Link>
        ))}
      </div>
    </main>
  );
}

Step 6: LLM-Optimized Markdown

Why Markdown? LLM crawlers (ChatGPT, Perplexity, Claude) prefer clean markdown over HTML. Reaudit includes markdownContent in every webhook — serve it for 3-5x higher AI citation rates.

6a. Create Markdown Endpoint

Serve markdown from your local database:

// app/blog/[slug]/markdown/route.ts
import { connectToDatabase } from '@/lib/db';
import { Article } from '@/models/Article';

export async function GET(req, { params }) {
  await connectToDatabase();
  const article = await Article.findOne({ slug: params.slug }).lean();

  if (!article?.markdownContent) {
    return new Response('Not Found', { status: 404 });
  }

  return new Response(article.markdownContent, {
    headers: {
      'Content-Type': 'text/markdown; charset=utf-8',
      'Cache-Control': 'public, max-age=3600',
    },
  });
}

6b. Add Alternate Link to Blog Pages

// In your blog page generateMetadata
export async function generateMetadata({ params }) {
  const article = await getArticle(params.slug);
  
  return {
    title: article.title,
    description: article.metaDescription,
    alternates: {
      types: {
        // Markdown version for LLM crawlers
        'text/markdown': `/blog/${article.slug}/markdown`,
      },
    },
  };
}

6c. Create Your Sitemap

Generate a sitemap pointing to YOUR domain:

// app/sitemap.ts
import { connectToDatabase } from '@/lib/db';
import { Article } from '@/models/Article';

export default async function sitemap() {
  await connectToDatabase();
  const articles = await Article.find({}).lean();

  return articles.map((article) => ({
    url: `https://yourdomain.com/blog/${article.slug}`,
    lastModified: article.publishedAt,
    changeFrequency: 'weekly',
    priority: 0.8,
  }));
}

6d. Update robots.txt

# public/robots.txt
User-agent: ChatGPT-User
User-agent: PerplexityBot
User-agent: ClaudeBot
User-agent: Google-Extended
Allow: /

Sitemap: https://your-domain.com/sitemap.xml

Benefits

  • 3-5x higher AI citation rates
  • Better ChatGPT visibility
  • Improved Perplexity ranking
  • Claude-friendly format

What's Included

  • Clean markdown content
  • Enhanced JSON-LD schema
  • SEO metadata & YAML frontmatter
  • Structured headings

Step 7: Deploy to Production

Add your webhook secret to your hosting platform:

Vercel

Project Settings → Environment Variables → Add REAUDIT_WEBHOOK_SECRET

Self-hosted / Docker

Add REAUDIT_WEBHOOK_SECRET to your .env or docker-compose.yml

Once deployed, set your webhook URL in the Reaudit dashboard (e.g., https://yoursite.com/api/reaudit-webhook). Content will be pushed automatically whenever you publish.

Advanced Examples

Filter by Category

// Filter by category from local DB
const seoArticles = await Article.find({
  section: 'blog',
  categories: 'AI Search',
}).lean();

Filter by Language

// Filter by language from local DB
const greekNews = await Article.find({
  section: 'news',
  language: 'el',
}).lean();

Pagination

const page = 2;
const limit = 12;

const articles = await Article.find({ section: 'blog' })
  .sort({ publishedAt: -1 })
  .skip((page - 1) * limit)
  .limit(limit)
  .lean();

const total = await Article.countDocuments({ section: 'blog' });
console.log('Total pages:', Math.ceil(total / limit));

Troubleshooting

Webhook not received

  • Webhook URL must be publicly accessible (not localhost)
  • Endpoint must accept POST requests
  • Check Reaudit dashboard for delivery errors

Signature verification failed

  • Use API Secret (not API Key) for verification
  • Hash the raw request body as text, not parsed JSON
  • Check for extra spaces in environment variable

Content not appearing on site

  • Check your database for the saved article
  • Verify the webhook handler saves correctly
  • Clear Next.js cache if using ISR: rm -rf .next

You're all set!

Your blog now receives content directly from Reaudit via webhooks. Content lives on your domain for maximum AI search visibility.

  • →Create more content in the Reaudit dashboard
  • →Configure content sections (Blog, News, etc.) for organization
  • →Add search functionality to your blog
  • →Set up multilingual content with language filters
Was this page helpful?

On this page

OverviewPrerequisitesEnvironment SetupArticle ModelWebhook EndpointBlog PageArticle ListingLLM OptimizationDeployAdvanced ExamplesTroubleshooting