JavaScript / TypeScript SDK

Einen richtig coolen Blog bauen — in Minuten

Von npm install bis zum live deployten, SEO-optimierten Headless-Blog mit Next.js, Astro oder Nuxt. Vollständig typisiert, zero dependencies.

lib/publino.ts
// lib/publino.ts
import { PublinoClient } from "@publino/sdk";

// Der Secret Key (pub_live_…) gehört ausschließlich auf den Server.
// In Next.js wird diese Datei nur in Server Components / Route Handlers importiert.
export const publino = new PublinoClient({
  apiKey: process.env.PUBLINO_API_KEY!,
  siteId: process.env.PUBLINO_SITE_ID!,
  // baseUrl ist optional – Standard: https://api.publino.de
});
Schritt 1 · Installation

SDK installieren & Keys hinterlegen

Das SDK funktioniert mit jedem Paketmanager. Den API-Key erzeugen Sie im Dashboard unter API & MCP → Delivery Keys. Nutzen Sie den Secret Key (pub_live_…) ausschließlich serverseitig.

Sicherheit: Der Secret Key darf niemals ins Browser-Bundle gelangen. Für rein clientseitige Nutzung gibt es den Publishable Key & embed.js.
Terminal
# npm
npm install @publino/sdk

# pnpm / yarn / bun
pnpm add @publino/sdk
yarn add @publino/sdk
bun add @publino/sdk
.env.local
# .env.local  —  niemals in den Browser bundeln!
PUBLINO_API_KEY=pub_live_xxxxxxxxxxxxxxxx
PUBLINO_SITE_ID=mein-blog
Schritt 2 · Client

Einen wiederverwendbaren Client anlegen

Eine zentrale lib/publino.ts kapselt Konfiguration und Keys. So importieren Sie den Client überall mit einer Zeile — und halten Secrets serverseitig.

  • Vollständig typisiert — Autovervollständigung für alle Felder
  • Automatisches Retry & ETag-Caching
  • Tree-shakeable, keine Drittabhängigkeiten
lib/publino.ts
// lib/publino.ts
import { PublinoClient } from "@publino/sdk";

// Der Secret Key (pub_live_…) gehört ausschließlich auf den Server.
// In Next.js wird diese Datei nur in Server Components / Route Handlers importiert.
export const publino = new PublinoClient({
  apiKey: process.env.PUBLINO_API_KEY!,
  siteId: process.env.PUBLINO_SITE_ID!,
  // baseUrl ist optional – Standard: https://api.publino.de
});
Schritt 3 · Übersicht

Die Blog-Übersicht bauen

articles.list() liefert alle veröffentlichten Artikel inklusive Titel, Auszug, Kategorie, Tags und Lesezeit. In einer React Server Component läuft der Abruf rein serverseitig — kein Loading-Spinner, kein Client-JS.

app/blog/page.tsx
// app/blog/page.tsx  —  Next.js App Router (React Server Component)
import Link from "next/link";
import { publino } from "@/lib/publino";

export const metadata = {
  title: "Blog",
  description: "Aktuelle Artikel, Tutorials und News.",
};

export default async function BlogPage() {
  // Nur veröffentlichte Artikel, neueste zuerst. list() gibt ein Array zurück.
  const articles = await publino.articles.list({
    locale: "de",
    limit: 12,
  });

  return (
    <main className="mx-auto max-w-3xl px-6 py-16">
      <h1 className="mb-10 text-4xl font-bold">Blog</h1>

      <div className="grid gap-8">
        {articles.map((article) => (
          <Link
            key={article.id}
            href={`/blog/${article.slug}-${article.id}`}
            className="group block"
          >
            {article.category && (
              <span className="text-sm font-semibold text-violet-600">
                {article.category.name}
              </span>
            )}
            <h2 className="mt-1 text-2xl font-semibold group-hover:underline">
              {article.title}
            </h2>
            {article.excerpt && (
              <p className="mt-2 text-gray-600">{article.excerpt}</p>
            )}
            <p className="mt-2 text-sm text-gray-400">
              {article.readingMinutes} Min. Lesezeit
            </p>
          </Link>
        ))}
      </div>
    </main>
  );
}
Schritt 4 · SEO

Einzelartikel mit perfektem SEO

Hier entscheidet sich das Ranking. Publino liefert pro Artikel Meta-Description, Canonical-URL und fertiges Schema.org-JSON-LD mit — Sie müssen es nur einsetzen. Mit generateMetadata bekommt jeder Artikel saubere Open-Graph-Tags.

app/blog/[ref]/page.tsx
// app/blog/[ref]/page.tsx  —  Einzelartikel mit perfektem SEO
import { notFound } from "next/navigation";
import type { Metadata } from "next";
import { publino } from "@/lib/publino";

type Props = { params: Promise<{ ref: string }> };

// 1. Meta-Tags pro Artikel — Title, Description, Canonical, Open Graph.
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { ref } = await params;
  const article = await publino.articles.get(ref, { locale: "de" });
  if (!article) return {};

  const canonical =
    article.canonicalUrl ?? `https://example.com/blog/${ref}`;

  return {
    title: article.title,
    description: article.metaDescription ?? article.excerpt ?? undefined,
    alternates: { canonical },
    openGraph: {
      title: article.title,
      description: article.metaDescription ?? undefined,
      type: "article",
      url: canonical,
      publishedTime: article.publishedAt ?? undefined,
    },
  };
}

export default async function ArticlePage({ params }: Props) {
  const { ref } = await params;
  const article = await publino.articles.get(ref, { locale: "de" });
  if (!article) notFound();

  return (
    <article className="mx-auto max-w-2xl px-6 py-16">
      {/* 2. Schema.org JSON-LD — Publino liefert fertiges BlogPosting-Markup mit. */}
      {article.jsonLd && (
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: JSON.stringify(article.jsonLd) }}
        />
      )}

      <h1 className="text-4xl font-bold">{article.title}</h1>
      <p className="mt-3 text-sm text-gray-400">
        {article.publishedAt &&
          new Date(article.publishedAt).toLocaleDateString("de-DE")}
        {" · "}
        {article.readingMinutes} Min.
      </p>

      {/* 3. Body als sicheres, server-gerendertes HTML. */}
      <div
        className="prose mt-8"
        dangerouslySetInnerHTML={{ __html: article.html }}
      />
    </article>
  );
}

Für maximale Geschwindigkeit (und beste Core Web Vitals) generieren Sie die Artikel-Pfade vorab statisch:

app/blog/[ref]/page.tsx
// Statische Pfade vorab generieren (SSG) — blitzschnelle Seiten, top Core Web Vitals.
export async function generateStaticParams() {
  const articles = await publino.articles.list({ locale: "de", limit: 100 });
  return articles.map((a) => ({ ref: `${a.slug}-${a.id}` }));
}
Kategorien & Tags

Filter & Navigation

Filtern Sie Artikel nach Kategorie- oder Tag-Slug und bauen Sie daraus eigene Übersichtsseiten — ideal für interne Verlinkung und Topic-Cluster, die das Ranking stärken.

categories.list() und tags.list() liefern alle Werte inkl. Artikel-Zähler für Ihre Navigation.

filter.ts
// Filter laufen über die Public-ID (das `id`-Feld von Kategorie/Tag).
const articles = await publino.articles.list({
  locale: "de",
  category: categoryId, // id aus categories.list()
  tag: tagId,           // id aus tags.list()
  limit: 20,
});

// Alle Kategorien & Tags der Site (z. B. für die Navigation)
const categories = await publino.categories.list({ locale: "de" });
const tags = await publino.tags.list({ locale: "de" });
Schritt 5 · Performance

Caching, ISR & Echtzeit-Updates

Statisch ausgeliefert, aber immer aktuell: Mit Incremental Static Regeneration baut Next.js Seiten im Hintergrund neu. Per Publino-Webhook revalidieren Sie einzelne Seiten in Sekunden, sobald ein Artikel erscheint.

app/api/revalidate/route.ts
// app/blog/page.tsx
// ISR: Seite alle 5 Minuten im Hintergrund neu bauen — frische Inhalte ohne Redeploy.
export const revalidate = 300;

// Sofort revalidieren per Publino-Webhook (article.published / .updated):
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";

export async function POST(req: Request) {
  const { slug, articleId } = await req.json();
  revalidatePath("/blog");
  revalidatePath(`/blog/${slug}-${articleId}`);
  return Response.json({ revalidated: true });
}
Weitere Frameworks

Dasselbe SDK, jeder Stack

Das SDK ist framework-agnostisch. Ein Auszug für Astro und Nuxt:

src/pages/blog/index.astro
---
// src/pages/blog/index.astro  —  Astro (komplett ohne Client-JS)
import { PublinoClient } from "@publino/sdk";

const publino = new PublinoClient({
  apiKey: import.meta.env.PUBLINO_API_KEY,
  siteId: "mein-blog",
});
const articles = await publino.articles.list({ locale: "de" });
---
<ul>
  {articles.map((a) => (
    <li><a href={`/blog/${a.slug}-${a.id}`}>{a.title}</a></li>
  ))}
</ul>
server/api/blog.get.ts
// server/api/blog.get.ts  —  Nuxt 3 (Nitro server route)
import { PublinoClient } from "@publino/sdk";

const publino = new PublinoClient({
  apiKey: process.env.PUBLINO_API_KEY!,
  siteId: "mein-blog",
});

export default defineEventHandler(async () => {
  return publino.articles.list({ locale: "de", limit: 12 });
});

Anderer Stack?

Auch für PHP und für reine HTML-Seiten ohne Backend gibt es einen direkten Weg zum Publino-Blog.