My personal site. theclashfruit.me
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: rss feed

+89 -8
+2 -8
app/(main)/post/[slug]/page.tsx
··· 6 6 7 7 import { eq } from 'drizzle-orm'; 8 8 9 - import { MDXRemote, type MDXRemoteOptions } from 'next-mdx-remote-client/rsc'; 9 + import { MDXRemote } from 'next-mdx-remote-client/rsc'; 10 + import options from '@/lib/mdx-remote-options'; 10 11 11 12 import { components } from '@/mdx-components'; 12 13 13 14 import Comment, { CommentWithReplies } from '@/components/Comment'; 14 15 15 16 import type { Metadata } from 'next'; 16 - 17 - const options: MDXRemoteOptions = { 18 - mdxOptions: { 19 - rehypePlugins: [rehypeStarryNight], 20 - remarkPlugins: [remarkGfm] 21 - } 22 - }; 23 17 24 18 export async function generateMetadata({ 25 19 params
+54
app/rss.xml/route.ts
··· 1 + import { db } from '@/lib/db/drizzle'; 2 + import { postsTable } from '@/lib/db/schema'; 3 + 4 + import { Feed } from 'feed'; 5 + 6 + import { evaluate } from 'next-mdx-remote-client/rsc'; 7 + import options from '@/lib/mdx-remote-options'; 8 + import { components } from '@/mdx-components'; 9 + 10 + export async function GET(req: Request) { 11 + const { renderToStaticMarkup } = await import('react-dom/server'); 12 + 13 + const posts = await db.select().from(postsTable); 14 + 15 + const feed = new Feed({ 16 + title: 'TheClashFruit', 17 + description: 18 + 'A fluffy dragon smashing buttons on a keyboard and drawing lines on paper.', 19 + id: 'https://theclashfruit.me', 20 + link: 'https://theclashfruit.me', 21 + language: 'en', 22 + favicon: 'https://theclashfruit.me/favicon.ico', 23 + copyright: `Copyright © ${new Date().getFullYear()} TheClashFruit, licensed under \`CC BY-NC-SA 4.0\` unless otherwise noted.` 24 + }); 25 + 26 + await Promise.all( 27 + posts.map(async (p) => { 28 + const { content } = await evaluate({ 29 + source: p.content, 30 + options, 31 + components 32 + }); 33 + 34 + feed.addItem({ 35 + title: p.title, 36 + id: `https://theclashfruit.me/post/${p.slug}`, 37 + link: `https://theclashfruit.me/post/${p.slug}`, 38 + description: p.excerpt ?? p.content, 39 + content: renderToStaticMarkup(content), 40 + date: p.publishedAt 41 + }); 42 + }) 43 + ); 44 + 45 + feed.addContributor({ 46 + name: 'TheClashFruit', 47 + email: 'admin@theclashfruit.me', 48 + link: 'https://theclashfruit.me' 49 + }); 50 + 51 + return new Response(feed.rss2(), { 52 + headers: new Headers({ 'content-type': 'application/xml' }) 53 + }); 54 + }
+13
lib/mdx-remote-options.ts
··· 1 + import type { MDXRemoteOptions } from 'next-mdx-remote-client/rsc'; 2 + 3 + import rehypeStarryNight from 'rehype-starry-night'; 4 + import remarkGfm from 'remark-gfm'; 5 + 6 + const options: MDXRemoteOptions = { 7 + mdxOptions: { 8 + rehypePlugins: [rehypeStarryNight], 9 + remarkPlugins: [remarkGfm] 10 + } 11 + }; 12 + 13 + export default options;
+1
package.json
··· 20 20 "@wooorm/starry-night": "^3.9.0", 21 21 "bcrypt": "^6.0.0", 22 22 "drizzle-orm": "^0.45.1", 23 + "feed": "^5.2.0", 23 24 "lucide-react": "^0.577.0", 24 25 "next": "16.1.6", 25 26 "next-mdx-remote-client": "^2.1.9",
+19
pnpm-lock.yaml
··· 41 41 drizzle-orm: 42 42 specifier: ^0.45.1 43 43 version: 0.45.1(pg@8.20.0) 44 + feed: 45 + specifier: ^5.2.0 46 + version: 5.2.0 44 47 lucide-react: 45 48 specifier: ^0.577.0 46 49 version: 0.577.0(react@19.2.3) ··· 2520 2523 picomatch: 2521 2524 optional: true 2522 2525 2526 + feed@5.2.0: 2527 + resolution: {integrity: sha512-hgH6CCb+7+0c8PBlakI2KubG6R+Rb1MhpNcdvqUXZTBwBHf32piwY255diAkAmkGZ6AWlywOU88AkOgP9q8Rdw==} 2528 + engines: {node: '>=20', pnpm: '>=10'} 2529 + 2523 2530 file-entry-cache@8.0.0: 2524 2531 resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 2525 2532 engines: {node: '>=16.0.0'} ··· 3776 3783 resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 3777 3784 engines: {node: '>=0.10.0'} 3778 3785 3786 + xml-js@1.6.11: 3787 + resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} 3788 + hasBin: true 3789 + 3779 3790 xtend@4.0.2: 3780 3791 resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} 3781 3792 engines: {node: '>=0.4'} ··· 6283 6294 optionalDependencies: 6284 6295 picomatch: 4.0.3 6285 6296 6297 + feed@5.2.0: 6298 + dependencies: 6299 + xml-js: 1.6.11 6300 + 6286 6301 file-entry-cache@8.0.0: 6287 6302 dependencies: 6288 6303 flat-cache: 4.0.1 ··· 8058 8073 isexe: 2.0.0 8059 8074 8060 8075 word-wrap@1.2.5: {} 8076 + 8077 + xml-js@1.6.11: 8078 + dependencies: 8079 + sax: 1.5.0 8061 8080 8062 8081 xtend@4.0.2: {} 8063 8082