Back to Docs
Releases & Changelog
Display your changelog and release notes in your app. Public releases require no authentication.
The pattern
- Newest page first, then "Load more". Fetch
limit=10, then passmeta.nextCursorascursorfor older releases. A button is one choice; infinite scroll or older/newer links work the same way, with the same cursor. - Server-render and cache it. In Next.js,
export const revalidate = 60: visitors get a cached page instantly, and new releases appear within about a minute. No setup needed. - If the fetch fails, throw. Don't render "no releases", or your cache may store the empty page.
- Content is markdown. Render it with a markdown library; never inject it as raw HTML.
Using a coding agent? Ask it to add a Simple Product changelog. It gets these same examples from the MCP setup guide.
// useReleases.ts: newest 10, then "Load more" pages with the cursor
import { useCallback, useEffect, useState } from 'react';
export interface Release {
id: string;
title: string;
content: string; // markdown
publishedAt: number;
}
export function useReleases(workspace: string, pageSize = 10) {
const [releases, setReleases] = useState<Release[]>([]);
const [cursor, setCursor] = useState<string | null>(null);
const [hasMore, setHasMore] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const fetchPage = useCallback(async (after: string | null) => {
setIsLoading(true);
const params = new URLSearchParams({ workspace, limit: String(pageSize) });
if (after) params.set('cursor', after);
const { data, meta } = await fetch(`https://simpleproduct.dev/api/v1/releases/public?${params}`).then((r) => r.json());
setReleases((prev) => (after ? [...prev, ...data] : data));
setCursor(meta.nextCursor);
setHasMore(meta.hasMore);
setIsLoading(false);
}, [workspace, pageSize]);
useEffect(() => { fetchPage(null); }, [fetchPage]);
return { releases, isLoading, hasMore, loadMore: () => fetchPage(cursor) };
}
// Changelog.tsx
import ReactMarkdown from 'react-markdown';
import { useReleases } from './useReleases';
export function Changelog({ workspace }: { workspace: string }) {
const { releases, isLoading, hasMore, loadMore } = useReleases(workspace);
return (
<div>
<h2>What's New</h2>
{releases.map((release) => (
<article key={release.id}>
<h3>{release.title}</h3>
<time>{new Date(release.publishedAt).toLocaleDateString()}</time>
{/* content is markdown */}
<ReactMarkdown>{release.content}</ReactMarkdown>
</article>
))}
{isLoading && <p>Loading…</p>}
{hasMore && !isLoading && <button onClick={loadMore}>Load more</button>}
</div>
);
}RSS Feed
Subscribe to releases via RSS. Great for letting users follow updates in their feed reader.
https://simpleproduct.dev/api/v1/releases/rss?workspace=YOUR_WORKSPACE_SLUGAPI Reference
GET /api/v1/releases/public
Get published releases, newest first, one page at a time. No authentication required.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
| workspace | Yes | Your workspace slug |
| limit | No | Releases per page, 1–100. Default 20. |
| cursor | No | meta.nextCursor from the previous page, to get the next one. Omit it for the newest. |
Response
{
"data": [
{ "id": "...", "title": "v2.1.0", "content": "## What's New...", "publishedAt": 1790000000000, "slug": "v2-1-0" }
],
"meta": { "count": 20, "nextCursor": "…", "hasMore": true }
}
Keep calling with cursor until hasMore is false. Responses can be cached: they send Cache-Control: s-maxage=60, stale-while-revalidate=300 and an ETag.
POST /api/v1/releases
Create a new release. Requires authentication.
Request Body
{
"title": "v2.1.0",
"content": "## What's New\n- Dark mode\n- Bug fixes",
"status": "published",
"publishedAt": 1234567890
}
Fields
| Field | Type | Description |
|---|---|---|
| title | string | Release title (e.g., "v2.1.0") |
| content | string | Release notes content (markdown) |
| status | string | "draft" or "published" |
| publishedAt | number | Unix timestamp (milliseconds) |
Tips
- •Cache responses: The API sends
Cache-Control: s-maxage=60and anETag, so CDNs and browsers can reuse responses. In Next.js, userevalidate. - •Offer RSS: Link to the RSS feed so users can follow updates in their preferred reader