Kit de blog para Next.js: posts con autores, categorías, etiquetas, series, feeds, comentarios moderados y newsletter
Instalar
genpm add @core/kit-blogQué obtienes
- Código en src/app/(blog)/, 32 archivos. (36,2 kB)
- Reglas de IA en src/app/(blog)/AGENTS.md, más archivos de reglas para tu IDE.
- Variables añadidas a .env.example: NEWSLETTER_FROM, NEWSLETTER_POSTAL_ADDRESS, NEWSLETTER_SECRET.
- Resuelve @core/blog, @core/comments, @core/kit-cms, @core/newsletter por ti.
README
Este paquete no tiene README.
Esto es exactamente lo que lee tu IA cuando trabaja en src/app/(blog). No se añade nada más a su contexto.
@core/kit-blog — rules for AI agents
Purpose
A complete blog on top of @core/kit-cms, edited in the same /admin: posts with rich text and cover images, author
profiles, categories, tags and series, scheduling, RSS/Atom/JSON feeds, site search, moderated comments, newsletter
with double opt-in and one-click unsubscribe, share links without third-party scripts and full SEO (BlogPosting
JSON-LD, sitemap, Open Graph). Glue and templates only: logic lives in @core/blog, comments and newsletter.
Map
_templates/genpm.md— the exact lines this kit adds to the project'ssrc/genpm/*.ts(generated, always current).blog/page.tsx— index (featured first,?page=).blog/[slug]/page.tsx— post.blog/{category,tag,author,series}/[slug]/page.tsx— archives (shared_lib/archive.tsx).blog/archive/[year]/[month].blog/feed.xml,blog/atom.xml,blog/feed.json— feeds.api/comments/[type]/[id]— comments (typepost).newsletter/{subscribe,confirm,unsubscribe}— routes;newsletter/{confirmed,unsubscribed,invalid}— pages._components/—PostList,PostCard,Comments,SubscribeForm,ShareLinks,blog.css(the visual template)._blocks/latest-posts.tsx— "Latest posts" block for CMS pages._lib/campaign.ts— "Create newsletter campaign" action.
Integration
- @core/kit-cms must be integrated first (follow its AGENTS.md if it was installed with this kit).
- Env:
NEWSLETTER_FROM,NEWSLETTER_POSTAL_ADDRESS,NEWSLETTER_SECRET(≥ 32 chars) and, in production,RESEND_API_KEY(see src/lib/newsletter/AGENTS.md). Explain to the person how to verify their sending domain (SPF, DKIM, DMARC) with their email provider; do not change their DNS yourself. - Migrations for @core/comments and @core/newsletter (src/lib/db/AGENTS.md).
- Add the lines of
_templates/genpm.mdto the project'ssrc/genpm/*.ts(it says which file and where): the blog collections, sitemap and search sources, thelatestPostsblock, and the admin resources (posts with "Create newsletter campaign", taxonomies, comments, newsletter) plus the pending-comments widget. - Add "Blog" to the header menu in Admin → Menus, and link the feeds from the site footer if wanted.
- Verify: create an author and a post, schedule it, check it is absent from
/blog, the feed and the sitemap until its time; comment as a guest (goes to moderation) and approve it; subscribe, confirm from the email and unsubscribe. Without an OAuth app nobody can sign in to the admin: an agent can create the author and posts withseedEntry/createEntry+publish(@core/content) from a script. WithoutRESEND_API_KEY, emails are printed to the console only undernext dev(next startis production and refuses to send without a provider).
Conventions
- Posts reference authors, categories, tags and series by slug; renaming a slug breaks links, so prefer editing names.
membersOnlyposts show only the excerpt here; with @core/billing, decide access withhasEntitlementinblog/[slug]/page.tsx.- The templates in
_componentsbelong to the project after installation: adapt markup andblog.cssfreely.
Don't
- Don't add third-party share or comment widgets (scripts and cookies without consent); keep plain links.
- Don't import subscriber lists without demonstrable consent, and never remove the unsubscribe link or postal address.
- Don't publish comments from guests without moderation unless the owner asks for it (
moderation: 'guests').
# @core/kit-blog — rules for AI agents
## Purpose
A complete blog on top of @core/kit-cms, edited in the same `/admin`: posts with rich text and cover images, author
profiles, categories, tags and series, scheduling, RSS/Atom/JSON feeds, site search, moderated comments, newsletter
with double opt-in and one-click unsubscribe, share links without third-party scripts and full SEO (BlogPosting
JSON-LD, sitemap, Open Graph). Glue and templates only: logic lives in @core/blog, comments and newsletter.
## Map
- `_templates/genpm.md` — the exact lines this kit adds to the project's `src/genpm/*.ts` (generated, always current).
- `blog/page.tsx` — index (featured first, `?page=`). `blog/[slug]/page.tsx` — post.
- `blog/{category,tag,author,series}/[slug]/page.tsx` — archives (shared `_lib/archive.tsx`). `blog/archive/[year]/[month]`.
- `blog/feed.xml`, `blog/atom.xml`, `blog/feed.json` — feeds. `api/comments/[type]/[id]` — comments (type `post`).
- `newsletter/{subscribe,confirm,unsubscribe}` — routes; `newsletter/{confirmed,unsubscribed,invalid}` — pages.
- `_components/` — `PostList`, `PostCard`, `Comments`, `SubscribeForm`, `ShareLinks`, `blog.css` (the visual template).
- `_blocks/latest-posts.tsx` — "Latest posts" block for CMS pages. `_lib/campaign.ts` — "Create newsletter campaign" action.
## Integration
1. @core/kit-cms must be integrated first (follow its AGENTS.md if it was installed with this kit).
2. Env: `NEWSLETTER_FROM`, `NEWSLETTER_POSTAL_ADDRESS`, `NEWSLETTER_SECRET` (≥ 32 chars) and, in production,
`RESEND_API_KEY` (see src/lib/newsletter/AGENTS.md). Explain to the person how to verify their sending domain
(SPF, DKIM, DMARC) with their email provider; do not change their DNS yourself.
3. Migrations for @core/comments and @core/newsletter (src/lib/db/AGENTS.md).
4. Add the lines of `_templates/genpm.md` to the project's `src/genpm/*.ts` (it says which file and where): the blog
collections, sitemap and search sources, the `latestPosts` block, and the admin resources (posts with "Create
newsletter campaign", taxonomies, comments, newsletter) plus the pending-comments widget.
5. Add "Blog" to the header menu in Admin → Menus, and link the feeds from the site footer if wanted.
6. Verify: create an author and a post, schedule it, check it is absent from `/blog`, the feed and the sitemap until
its time; comment as a guest (goes to moderation) and approve it; subscribe, confirm from the email and unsubscribe.
Without an OAuth app nobody can sign in to the admin: an agent can create the author and posts with `seedEntry` /
`createEntry` + `publish` (@core/content) from a script. Without `RESEND_API_KEY`, emails are printed to the console
only under `next dev` (`next start` is production and refuses to send without a provider).
## Conventions
- Posts reference authors, categories, tags and series by slug; renaming a slug breaks links, so prefer editing names.
- `membersOnly` posts show only the excerpt here; with @core/billing, decide access with `hasEntitlement` in `blog/[slug]/page.tsx`.
- The templates in `_components` belong to the project after installation: adapt markup and `blog.css` freely.
## Don't
- Don't add third-party share or comment widgets (scripts and cookies without consent); keep plain links.
- Don't import subscriber lists without demonstrable consent, and never remove the unsubscribe link or postal address.
- Don't publish comments from guests without moderation unless the owner asks for it (`moderation: 'guests'`).
El árbol exacto que se inyectará, tras aplicar .genpmignore. Anclado a
// Post: cabecera, portada, contenido, autores, compartir, relacionados, comentarios y alta en la newsletter.
import type { Metadata } from 'next';
import '../../_components/blog.css';
import { draftMode } from 'next/headers';
import { notFound } from 'next/navigation';
import { getPost, relatedPosts } from '@/lib/blog';
import { RichText } from '@/lib/rich-text';
import { absoluteUrl, buildMetadata, jsonLd, jsonLdScript } from '@/lib/seo';
import { getSiteSettings } from '@/lib/site';
import { Comments } from '../../_components/comments';
import { PostCard } from '../../_components/post-card';
import { ShareLinks } from '../../_components/share-links';
import { SubscribeForm } from '../../_components/subscribe-form';
import { formatDate, mediaImage, postPath } from '../../_lib/blog';
type Props = { params: Promise<{ slug: string }>; searchParams: Promise<Record<string, string | string[] | undefined>> };
const one = (v: string | string[] | undefined) => (Array.isArray(v) ? v[0] : v);
async function load(slug: string) {
const { isEnabled } = await draftMode();
return getPost(slug, { draft: isEnabled });
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await load((await params).slug);
if (!post) return {};
const cover = await mediaImage(post.cover);
return (await buildMetadata({
title: post.data.seo?.title ?? post.title,
description: post.data.seo?.description ?? post.excerpt,
path: postPath(post),
...(cover && { image: cover.src }),
...((post.data.seo?.noindex || !post.publishedAt) && { noindex: true }),
})) as Metadata;
}
export default async function PostPage({ params, searchParams }: Props) {
const post = await load((await params).slug);
if (!post) notFound();
const sp = await searchParams;
const [cover, related, settings] = await Promise.all([mediaImage(post.cover, { alt: post.coverAlt ?? '' }), relatedPosts(post), getSiteSettings()]);
const authors = post.authorProfiles.map((a) => a.name).join(', ');
const ld =
post.publishedAt &&
jsonLd.article({
title: post.title,
path: postPath(post),
description: post.excerpt,
publishedAt: post.publishedAt,
modifiedAt: post.updatedAt,
...(cover && { image: cover.src }),
...(authors && { author: authors }),
...(settings && { publisher: settings }),
});
return (
<main className="blk blog-post">
<article aria-labelledby="post-title">
<header>
<p className="blog-meta">
{post.categoryList.map((c, i) => (
<span key={c.slug}>
{i > 0 && ', '}
<a href={`/blog/category/${c.slug}`}>{c.name}</a>
</span>
))}
</p>
<h1 id="post-title">{post.title}</h1>
<p className="blog-meta">
{authors && (
<>
By{' '}
{post.authorProfiles.map((a, i) => (
<span key={a.slug}>
{i > 0 && ', '}
<a href={`/blog/author/${a.slug}`}>{a.name}</a>
</span>
))}{' '}
·{' '}
</>
)}
{post.publishedAt && <time dateTime={post.publishedAt.toISOString()}>{formatDate(post.publishedAt)}</time>} · {post.readingMinutes} min read
</p>
</header>
{cover && <img {...cover} className="blog-post__cover" loading="eager" decoding="async" fetchPriority="high" alt={cover.alt} />}
{post.membersOnly ? (
<div className="ui-alert">
<p>{post.excerpt}</p>
<p>This post is for members only.</p>
</div>
) : (
<div className="blog-post__body">
<RichText doc={post.data.body} />
</div>
)}
{post.tagList.length > 0 && (
<p className="blog-tags">
Tags:{' '}
{post.tagList.map((t, i) => (
<span key={t.slug}>
{i > 0 && ', '}
<a href={`/blog/tag/${t.slug}`}>{t.name}</a>
</span>
))}
</p>
)}
<ShareLinks url={absoluteUrl(postPath(post))} title={post.title} />
</article>
{related.length > 0 && (
<section aria-labelledby="related-title">
<h2 id="related-title">Related posts</h2>
<ul className="blk-grid blk-grid--3" role="list">
{related.map((r) => (
<li key={r.id}>
<PostCard post={r} level={3} />
</li>
))}
</ul>
</section>
)}
<SubscribeForm status={one(sp.newsletter)} />
<Comments postId={post.id} status={one(sp.comment)} />
{ld && <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: jsonLdScript(ld) }} />}
</main>
);
}
Este paquete no declara servidores MCP.
| Versión | Commit | Publicado | Escaneo |
|---|---|---|---|
| 1.0.1 | fa952a4 | hace 4 horas | escaneo superado |
- propuesta
- GenPM propone el comando npm y solo lo ejecuta si dices que sí.
- Usado por (0)
- Aún no depende de él ningún paquete público.
- escaneo
- escaneo superado · 0 hallazgos
- commit
- v1.0.1 → fa952a4b7c71340c947de8bef049f8d9c18f7871 · verificado tras la descarga
- scripts
- Ninguno. GenPM nunca ejecuta código del paquete.
- licencia
- MIT
- Calidad
- 100/100
- Licencia reconocidacumplido
- AGENTS.md explica su propósitocumplido
- AGENTS.md tiene pasos de integracióncumplido
- AGENTS.md lista convenciones o prohibicionescumplido
- Incluye testscumplido
- Escaneo de seguridad superadocumplido
- Publicado en los últimos 6 mesescumplido
- Publicador verificadocumplido
- Resumen y palabras clavecumplido
- reporte
- ¿Ves algo raro?