Documentation Index
Fetch the complete documentation index at: https://docs.usenotra.com/llms.txt
Use this file to discover all available pages before exploring further.
When caching Notra post data, include every query input that changes the result set in the cache key.
For list posts, that usually means:
page
limit
sort
status
contentType
Cache key design
Treat each unique list query as its own cache entry.
type ListPostsParams = {
page?: number;
limit?: number;
sort?: "asc" | "desc";
status?: Array<"draft" | "published">;
contentType?: Array<
"changelog" | "linkedin_post" | "twitter_post" | "blog_post"
>;
};
function createPostsCacheKey(params: ListPostsParams) {
return [
"notra",
"posts",
params.page ?? 1,
params.limit ?? 10,
params.sort ?? "desc",
[...(params.status ?? [])].sort().join(","),
[...(params.contentType ?? [])].sort().join(","),
] as const;
}
Sorting array filters before building the key helps avoid duplicate cache entries for equivalent queries.
Recommended strategy
Use separate caches for:
- post lists, keyed by pagination and filters
- individual posts, keyed by
postId
This keeps list pages independent while still letting you refresh a single post after PATCH /v1/posts/{postId}.
const listKey = createPostsCacheKey({
page: 2,
limit: 20,
sort: "desc",
status: ["published"],
contentType: ["blog_post"],
});
const postKey = ["notra", "post", "post_abc"] as const;
Invalidation rules
Invalidate caches whenever the underlying list order or membership can change.
After a post update
After PATCH /v1/posts/{postId} succeeds:
- invalidate the individual post cache for that
postId
- invalidate all cached post lists, because title, status, and updated content can affect what users should see
After a new generated post becomes available
When a generation job finishes and a new post is created:
- invalidate all cached post lists
- optionally prefetch page 1 again if your UI shows newest posts first with
sort=desc
After a delete
After deleting a post:
- remove the individual post cache
- invalidate all cached post lists so pagination totals and offsets stay correct
Example with TanStack Query
import {
QueryClient,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
function usePosts(params: ListPostsParams) {
return useQuery({
queryKey: createPostsCacheKey(params),
queryFn: async () => {
const search = new URLSearchParams({
page: String(params.page ?? 1),
limit: String(params.limit ?? 10),
sort: params.sort ?? "desc",
});
for (const status of params.status ?? []) search.append("status", status);
for (const type of params.contentType ?? []) {
search.append("contentType", type);
}
const response = await fetch(
`https://api.usenotra.com/v1/posts?${search.toString()}`,
{
headers: {
Authorization: `Bearer ${process.env.NOTRA_API_KEY}`,
},
}
);
if (!response.ok) throw new Error("Failed to fetch posts");
return response.json();
},
});
}
function useUpdatePost() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
postId,
updates,
}: {
postId: string;
updates: { title?: string; markdown?: string; status?: "draft" | "published" };
}) => {
const response = await fetch(`https://api.usenotra.com/v1/posts/${postId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.NOTRA_API_KEY}`,
},
body: JSON.stringify(updates),
});
if (!response.ok) throw new Error("Failed to update post");
return response.json();
},
onSuccess: (_data, { postId }) => {
queryClient.invalidateQueries({ queryKey: ["notra", "posts"] });
queryClient.invalidateQueries({ queryKey: ["notra", "post", postId] });
},
});
}
React and Next.js notes
If you use React Server Components or Next.js, React’s cache() function can help dedupe repeated reads during a single render on the server.
Use it for read paths, but do not rely on it alone for mutation invalidation. Pair it with route-level revalidation, cache tags, or your client cache invalidation strategy after updates, deletes, or completed generation jobs.
Practical defaults
- Use a short TTL for list caches if content changes frequently.
- Use explicit invalidation after
update, delete, and successful generation completion.
- Prefer
sort=desc plus page-1 refetch if your UI is a newest-first feed.
- Cache individual posts separately from paginated lists.