Examples

Complete example implementations for different frameworks and use cases.

Next.js Blog

A complete blog implementation with ISR, sitemap, and SEO.

// Project structure
├── app/
│   ├── blog/
│   │   ├── page.tsx           # Blog listing
│   │   └── [slug]/
│   │       └── page.tsx       # Article page
│   ├── sitemap.ts             # Sitemap
│   └── layout.tsx
├── lib/
│   └── trident.ts             # Client config
└── styles/
    └── trident.css            # Component styles
lib/trident.ts
import { createTridentClient } from "@trident/client";

export const trident = createTridentClient({
  apiKey: process.env.TRIDENT_API_KEY!,
});
app/blog/page.tsx
import { trident } from "@/lib/trident";
import Link from "next/link";
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "Blog | My Company",
  description: "Latest articles and insights",
};

export default async function BlogPage() {
  const { data: articles } = await trident.getArticles({
    limit: 20,
  });

  return (
    <main className="mx-auto max-w-4xl px-4 py-16">
      <h1 className="text-4xl font-bold">Blog</h1>
      <div className="mt-12 divide-y">
        {articles.map((article) => (
          <article key={article.id} className="py-8">
            <Link href={`/blog/${article.slug}`}>
              <h2 className="text-2xl font-semibold hover:text-blue-600">
                {article.title}
              </h2>
            </Link>
            <p className="mt-3 text-gray-600">{article.excerpt}</p>
            <div className="mt-4 flex items-center gap-4 text-sm text-gray-500">
              <time>
                {new Date(article.publishedAt!).toLocaleDateString()}
              </time>
              {article.wordCount && (
                <span>{Math.ceil(article.wordCount / 200)} min read</span>
              )}
            </div>
          </article>
        ))}
      </div>
    </main>
  );
}

export const revalidate = 3600;
app/blog/[slug]/page.tsx
import { trident } from "@/lib/trident";
import { ArticleRenderer, StructuredData, FAQSchema } from "@trident/client/components";
import { generateArticleMetadata } from "@trident/client/nextjs";
import { notFound } from "next/navigation";

interface Props {
  params: { slug: string };
}

export async function generateMetadata({ params }: Props) {
  try {
    const article = await trident.getArticle(params.slug);
    return generateArticleMetadata(article, {
      baseUrl: "https://mycompany.com",
    });
  } catch {
    return { title: "Article Not Found" };
  }
}

export async function generateStaticParams() {
  const entries = await trident.getSitemap();
  return entries.map((entry) => ({ slug: entry.slug }));
}

export default async function ArticlePage({ params }: Props) {
  try {
    const article = await trident.getArticle(params.slug);

    return (
      <>
        <StructuredData
          article={article}
          baseUrl="https://mycompany.com"
          organization={{
            name: "My Company",
            logo: "https://mycompany.com/logo.png",
          }}
        />
        {article.content?.faq && <FAQSchema questions={article.content.faq} />}

        <article className="mx-auto max-w-3xl px-4 py-16">
          <header className="mb-12">
            <h1 className="text-4xl font-bold">{article.title}</h1>
            <div className="mt-4 flex items-center gap-4 text-gray-500">
              <time>{new Date(article.publishedAt!).toLocaleDateString()}</time>
              {article.wordCount && (
                <span>{Math.ceil(article.wordCount / 200)} min read</span>
              )}
            </div>
          </header>

          {article.content && (
            <ArticleRenderer article={article} className="prose prose-lg" />
          )}
        </article>
      </>
    );
  } catch {
    notFound();
  }
}

export const revalidate = 3600;
app/sitemap.ts
import { trident } from "@/lib/trident";
import { generateSitemapEntries } from "@trident/client/nextjs";

export default async function sitemap() {
  const entries = await trident.getSitemap();

  const articleEntries = generateSitemapEntries(entries, {
    baseUrl: "https://mycompany.com",
    urlPattern: "/blog/:slug",
  });

  return [
    { url: "https://mycompany.com", lastModified: new Date() },
    { url: "https://mycompany.com/about", lastModified: new Date() },
    ...articleEntries,
  ];
}

export const revalidate = 3600;
styles/trident.css
/* Trident component styles */
[data-trident-paragraph] {
  @apply mb-6 leading-relaxed text-gray-700;
}

[data-trident-heading="2"] {
  @apply mb-4 mt-10 text-2xl font-bold text-gray-900;
}

[data-trident-heading="3"] {
  @apply mb-3 mt-8 text-xl font-semibold text-gray-900;
}

[data-trident-list] {
  @apply my-6 space-y-2 pl-6;
}

[data-trident-list="ordered"] {
  @apply list-decimal;
}

[data-trident-list="unordered"] {
  @apply list-disc;
}

[data-trident-callout] {
  @apply my-6 rounded-lg border-l-4 p-4;
}

[data-trident-callout="tip"] {
  @apply border-green-500 bg-green-50;
}

[data-trident-callout="warning"] {
  @apply border-yellow-500 bg-yellow-50;
}

[data-trident-callout="note"] {
  @apply border-blue-500 bg-blue-50;
}

[data-trident-table] {
  @apply my-6 w-full border-collapse text-left;
}

[data-trident-th],
[data-trident-td] {
  @apply border border-gray-300 px-4 py-2;
}

[data-trident-th] {
  @apply bg-gray-100 font-semibold;
}

[data-trident-code] {
  @apply my-6 overflow-x-auto rounded-lg bg-gray-900 p-4 text-sm text-gray-100;
}

[data-trident-image] {
  @apply my-6;
}

[data-trident-image] img {
  @apply rounded-lg;
}

[data-trident-caption] {
  @apply mt-2 text-center text-sm text-gray-500;
}

[data-trident-quote] {
  @apply my-6 border-l-4 border-gray-300 pl-4 italic text-gray-600;
}

[data-trident-faq] {
  @apply my-8 space-y-6;
}

[data-trident-faq-item] {
  @apply rounded-lg border border-gray-200 p-6;
}

[data-trident-faq-question] {
  @apply text-lg font-semibold text-gray-900;
}

[data-trident-faq-answer] {
  @apply mt-3 text-gray-600;
}

[data-trident-sources] {
  @apply mt-12 border-t pt-8;
}

[data-trident-cta] {
  @apply my-12 rounded-xl bg-blue-50 p-8 text-center;
}

Framework-Agnostic Usage

Use the core client without React components:

import { createTridentClient } from "@trident/client";
import {
  contentToHtml,
  contentToText,
  calculateReadingTime,
} from "@trident/client/generic";

const client = createTridentClient({
  apiKey: process.env.TRIDENT_API_KEY!,
});

// Fetch article
const article = await client.getArticle("my-article");

// Convert to HTML (for SSR without React)
const html = contentToHtml(article.content);

// Convert to plain text (for search indexing)
const text = contentToText(article.content);

// Calculate reading time
const minutes = calculateReadingTime(article.content);

Multi-Product Setup

Handle multiple products with dynamic routes:

// app/[product]/blog/[slug]/page.tsx
interface Props {
  params: { product: string; slug: string };
}

export async function generateStaticParams() {
  const entries = await trident.getSitemap();

  return entries.map((entry) => ({
    product: entry.productSlug,
    slug: entry.slug,
  }));
}

export default async function ArticlePage({ params }: Props) {
  const article = await trident.getArticle(params.slug, {
    product: params.product,
  });
  // ...
}

More Examples

Check out the examples directory on GitHub for complete working examples including:

  • • Next.js App Router with Tailwind CSS
  • • Next.js Pages Router
  • • Remix
  • • Astro
  • • Multi-territory/multi-language setup