Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import { Route as rootRouteImport } from './routes/__root'
import { Route as SponsorsEmbedRouteImport } from './routes/sponsors-embed'
import { Route as RssDotxmlRouteImport } from './routes/rss[.]xml'
import { Route as PartnersEmbedRouteImport } from './routes/partners-embed'
import { Route as MerchRouteImport } from './routes/merch'
import { Route as LlmsDottxtRouteImport } from './routes/llms[.]txt'
Expand Down Expand Up @@ -44,7 +45,6 @@ import { Route as LibrariesMaintainersRouteImport } from './routes/_libraries/ma
import { Route as LibrariesLoginRouteImport } from './routes/_libraries/login'
import { Route as LibrariesLearnRouteImport } from './routes/_libraries/learn'
import { Route as LibrariesFeedbackLeaderboardRouteImport } from './routes/_libraries/feedback-leaderboard'
import { Route as LibrariesExploreRouteImport } from './routes/_libraries/explore'
import { Route as LibrariesEthosRouteImport } from './routes/_libraries/ethos'
import { Route as LibrariesDashboardRouteImport } from './routes/_libraries/dashboard'
import { Route as LibrariesBrandGuideRouteImport } from './routes/_libraries/brand-guide'
Expand Down Expand Up @@ -108,6 +108,11 @@ const SponsorsEmbedRoute = SponsorsEmbedRouteImport.update({
path: '/sponsors-embed',
getParentRoute: () => rootRouteImport,
} as any)
const RssDotxmlRoute = RssDotxmlRouteImport.update({
id: '/rss.xml',
path: '/rss.xml',
getParentRoute: () => rootRouteImport,
} as any)
const PartnersEmbedRoute = PartnersEmbedRouteImport.update({
id: '/partners-embed',
path: '/partners-embed',
Expand Down Expand Up @@ -278,11 +283,6 @@ const LibrariesFeedbackLeaderboardRoute =
path: '/feedback-leaderboard',
getParentRoute: () => LibrariesRouteRoute,
} as any)
const LibrariesExploreRoute = LibrariesExploreRouteImport.update({
id: '/explore',
path: '/explore',
getParentRoute: () => LibrariesRouteRoute,
} as any)
const LibrariesEthosRoute = LibrariesEthosRouteImport.update({
id: '/ethos',
path: '/ethos',
Expand Down Expand Up @@ -599,6 +599,7 @@ export interface FileRoutesByFullPath {
'/llms.txt': typeof LlmsDottxtRoute
'/merch': typeof MerchRoute
'/partners-embed': typeof PartnersEmbedRoute
'/rss.xml': typeof RssDotxmlRoute
'/sponsors-embed': typeof SponsorsEmbedRoute
'/$libraryId/$version': typeof LibraryIdVersionRouteWithChildren
'/account': typeof LibrariesAccountRouteWithChildren
Expand All @@ -607,7 +608,6 @@ export interface FileRoutesByFullPath {
'/brand-guide': typeof LibrariesBrandGuideRoute
'/dashboard': typeof LibrariesDashboardRoute
'/ethos': typeof LibrariesEthosRoute
'/explore': typeof LibrariesExploreRoute
'/feedback-leaderboard': typeof LibrariesFeedbackLeaderboardRoute
'/learn': typeof LibrariesLearnRoute
'/login': typeof LibrariesLoginRoute
Expand Down Expand Up @@ -691,13 +691,13 @@ export interface FileRoutesByTo {
'/llms.txt': typeof LlmsDottxtRoute
'/merch': typeof MerchRoute
'/partners-embed': typeof PartnersEmbedRoute
'/rss.xml': typeof RssDotxmlRoute
'/sponsors-embed': typeof SponsorsEmbedRoute
'/$libraryId/$version': typeof LibraryIdVersionRouteWithChildren
'/ads': typeof LibrariesAdsRoute
'/brand-guide': typeof LibrariesBrandGuideRoute
'/dashboard': typeof LibrariesDashboardRoute
'/ethos': typeof LibrariesEthosRoute
'/explore': typeof LibrariesExploreRoute
'/feedback-leaderboard': typeof LibrariesFeedbackLeaderboardRoute
'/learn': typeof LibrariesLearnRoute
'/login': typeof LibrariesLoginRoute
Expand Down Expand Up @@ -784,6 +784,7 @@ export interface FileRoutesById {
'/llms.txt': typeof LlmsDottxtRoute
'/merch': typeof MerchRoute
'/partners-embed': typeof PartnersEmbedRoute
'/rss.xml': typeof RssDotxmlRoute
'/sponsors-embed': typeof SponsorsEmbedRoute
'/$libraryId/$version': typeof LibraryIdVersionRouteWithChildren
'/_libraries/account': typeof LibrariesAccountRouteWithChildren
Expand All @@ -792,7 +793,6 @@ export interface FileRoutesById {
'/_libraries/brand-guide': typeof LibrariesBrandGuideRoute
'/_libraries/dashboard': typeof LibrariesDashboardRoute
'/_libraries/ethos': typeof LibrariesEthosRoute
'/_libraries/explore': typeof LibrariesExploreRoute
'/_libraries/feedback-leaderboard': typeof LibrariesFeedbackLeaderboardRoute
'/_libraries/learn': typeof LibrariesLearnRoute
'/_libraries/login': typeof LibrariesLoginRoute
Expand Down Expand Up @@ -880,6 +880,7 @@ export interface FileRouteTypes {
| '/llms.txt'
| '/merch'
| '/partners-embed'
| '/rss.xml'
| '/sponsors-embed'
| '/$libraryId/$version'
| '/account'
Expand All @@ -888,7 +889,6 @@ export interface FileRouteTypes {
| '/brand-guide'
| '/dashboard'
| '/ethos'
| '/explore'
| '/feedback-leaderboard'
| '/learn'
| '/login'
Expand Down Expand Up @@ -972,13 +972,13 @@ export interface FileRouteTypes {
| '/llms.txt'
| '/merch'
| '/partners-embed'
| '/rss.xml'
| '/sponsors-embed'
| '/$libraryId/$version'
| '/ads'
| '/brand-guide'
| '/dashboard'
| '/ethos'
| '/explore'
| '/feedback-leaderboard'
| '/learn'
| '/login'
Expand Down Expand Up @@ -1064,6 +1064,7 @@ export interface FileRouteTypes {
| '/llms.txt'
| '/merch'
| '/partners-embed'
| '/rss.xml'
| '/sponsors-embed'
| '/$libraryId/$version'
| '/_libraries/account'
Expand All @@ -1072,7 +1073,6 @@ export interface FileRouteTypes {
| '/_libraries/brand-guide'
| '/_libraries/dashboard'
| '/_libraries/ethos'
| '/_libraries/explore'
| '/_libraries/feedback-leaderboard'
| '/_libraries/learn'
| '/_libraries/login'
Expand Down Expand Up @@ -1160,6 +1160,7 @@ export interface RootRouteChildren {
LlmsDottxtRoute: typeof LlmsDottxtRoute
MerchRoute: typeof MerchRoute
PartnersEmbedRoute: typeof PartnersEmbedRoute
RssDotxmlRoute: typeof RssDotxmlRoute
SponsorsEmbedRoute: typeof SponsorsEmbedRoute
ApiUploadthingRoute: typeof ApiUploadthingRoute
AuthPopupSuccessRoute: typeof AuthPopupSuccessRoute
Expand All @@ -1186,6 +1187,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SponsorsEmbedRouteImport
parentRoute: typeof rootRouteImport
}
'/rss.xml': {
id: '/rss.xml'
path: '/rss.xml'
fullPath: '/rss.xml'
preLoaderRoute: typeof RssDotxmlRouteImport
parentRoute: typeof rootRouteImport
}
'/partners-embed': {
id: '/partners-embed'
path: '/partners-embed'
Expand Down Expand Up @@ -1424,13 +1432,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof LibrariesFeedbackLeaderboardRouteImport
parentRoute: typeof LibrariesRouteRoute
}
'/_libraries/explore': {
id: '/_libraries/explore'
path: '/explore'
fullPath: '/explore'
preLoaderRoute: typeof LibrariesExploreRouteImport
parentRoute: typeof LibrariesRouteRoute
}
'/_libraries/ethos': {
id: '/_libraries/ethos'
path: '/ethos'
Expand Down Expand Up @@ -1930,7 +1931,6 @@ interface LibrariesRouteRouteChildren {
LibrariesBrandGuideRoute: typeof LibrariesBrandGuideRoute
LibrariesDashboardRoute: typeof LibrariesDashboardRoute
LibrariesEthosRoute: typeof LibrariesEthosRoute
LibrariesExploreRoute: typeof LibrariesExploreRoute
LibrariesFeedbackLeaderboardRoute: typeof LibrariesFeedbackLeaderboardRoute
LibrariesLearnRoute: typeof LibrariesLearnRoute
LibrariesLoginRoute: typeof LibrariesLoginRoute
Expand Down Expand Up @@ -1967,7 +1967,6 @@ const LibrariesRouteRouteChildren: LibrariesRouteRouteChildren = {
LibrariesBrandGuideRoute: LibrariesBrandGuideRoute,
LibrariesDashboardRoute: LibrariesDashboardRoute,
LibrariesEthosRoute: LibrariesEthosRoute,
LibrariesExploreRoute: LibrariesExploreRoute,
LibrariesFeedbackLeaderboardRoute: LibrariesFeedbackLeaderboardRoute,
LibrariesLearnRoute: LibrariesLearnRoute,
LibrariesLoginRoute: LibrariesLoginRoute,
Expand Down Expand Up @@ -2066,6 +2065,7 @@ const rootRouteChildren: RootRouteChildren = {
LlmsDottxtRoute: LlmsDottxtRoute,
MerchRoute: MerchRoute,
PartnersEmbedRoute: PartnersEmbedRoute,
RssDotxmlRoute: RssDotxmlRoute,
SponsorsEmbedRoute: SponsorsEmbedRoute,
ApiUploadthingRoute: ApiUploadthingRoute,
AuthPopupSuccessRoute: AuthPopupSuccessRoute,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/_libraries/blog.$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function BlogPost() {

const blogContent = `<small>_by ${formatAuthors(authors)} on ${format(
new Date(published || 0),
'MMM dd, yyyy',
'MMMM d, yyyy',
)}._</small>

${content}`
Expand Down
19 changes: 16 additions & 3 deletions src/routes/_libraries/blog.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Footer } from '~/components/Footer'
import { PostNotFound } from './blog'
import { createServerFn } from '@tanstack/react-start'
import { setResponseHeaders } from '@tanstack/react-start/server'
import { RssIcon } from 'lucide-react'

type BlogFrontMatter = {
slug: string
Expand Down Expand Up @@ -65,7 +66,19 @@ function BlogIndex() {
<div className="flex flex-col max-w-full min-h-screen gap-12 p-4 md:p-8 pb-0">
<div className="flex-1 space-y-12 w-full max-w-4xl mx-auto">
<header className="">
<h1 className="text-3xl font-black">Blog</h1>
<div className="flex gap-3 items-baseline">
<h1 className="text-3xl font-black">Blog</h1>
<a
href="/rss.xml"
target="_blank"
rel="noreferrer"
className="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors text-xl"
title="RSS Feed"
>
<RssIcon />
</a>
</div>

<p className="text-lg mt-4 text-gray-600 dark:text-gray-400">
The latest news and blog posts from TanStack
</p>
Expand All @@ -88,10 +101,10 @@ function BlogIndex() {
{published ? (
<time
dateTime={published}
title={format(new Date(published), 'MMM dd, yyyy')}
title={format(new Date(published), 'MMM d, yyyy')}
>
{' '}
on {format(new Date(published), 'MMM dd, yyyy')}
on {format(new Date(published), 'MMM d, yyyy')}
</time>
) : null}
</p>
Expand Down
84 changes: 84 additions & 0 deletions src/routes/rss[.]xml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { createFileRoute } from '@tanstack/react-router'
import { setResponseHeader } from '@tanstack/react-start/server'
import { getPublishedPosts, formatAuthors } from '~/utils/blog'

function escapeXml(unsafe: string): string {
return unsafe
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}

function generateRSSFeed() {
const posts = getPublishedPosts().slice(0, 50) // Most recent 50 posts
const siteUrl = 'https://tanstack.com'
const buildDate = new Date().toUTCString()

const rssItems = posts
.map((post) => {
const postUrl = `${siteUrl}/blog/${post.slug}`
const pubDate = new Date(post.published).toUTCString()
const author = formatAuthors(post.authors)

// Use excerpt if available, otherwise try to get first paragraph from content
let description = post.excerpt || ''
if (!description && post.content) {
// Extract first paragraph after frontmatter
const contentWithoutFrontmatter = post.content
.replace(/^---[\s\S]*?---/, '')
.trim()
const firstParagraph = contentWithoutFrontmatter.split('\n\n')[0]
description = firstParagraph.replace(/!\[[^\]]*\]\([^)]*\)/g, '') // Remove images
}

return `
<item>
<title>${escapeXml(post.title)}</title>
<link>${escapeXml(postUrl)}</link>
<guid isPermaLink="true">${escapeXml(postUrl)}</guid>
<pubDate>${pubDate}</pubDate>
<author>${escapeXml(author)}</author>
<description>${escapeXml(description)}</description>
${post.headerImage ? `<enclosure url="${escapeXml(siteUrl + post.headerImage)}" type="image/png" />` : ''}
</item>`
})
.join('')

return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>TanStack Blog</title>
<link>${siteUrl}/blog</link>
<description>The latest news and updates from TanStack</description>
<language>en-us</language>
<lastBuildDate>${buildDate}</lastBuildDate>
<atom:link href="${siteUrl}/rss.xml" rel="self" type="application/rss+xml" />
${rssItems}
</channel>
</rss>`
}

export const Route = createFileRoute('/rss.xml')({
// @ts-ignore server property not in route types yet
server: {
handlers: {
GET: async () => {
const content = generateRSSFeed()

setResponseHeader('Content-Type', 'application/xml; charset=utf-8')
setResponseHeader(
'Cache-Control',
'public, max-age=300, must-revalidate',
)
setResponseHeader(
'CDN-Cache-Control',
'max-age=3600, stale-while-revalidate=3600',
)

return new Response(content)
},
},
},
})
11 changes: 6 additions & 5 deletions src/utils/dates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function format(date: Date | number, formatStr: string): string {
// Common format patterns
switch (formatStr) {
case 'PPP':
case 'MMMM d, yyyy':
// "April 29, 2023"
return d.toLocaleDateString('en-US', {
year: 'numeric',
Expand All @@ -65,16 +66,16 @@ export function format(date: Date | number, formatStr: string): string {
day: '2-digit',
})

case 'MMMM d, yyyy':
// "April 29, 2023"
case 'MMM d, yyyy':
// "Apr 29, 2023"
return d.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
month: 'short',
day: 'numeric',
})

case 'MMM d, yyyy':
// "Apr 29, 2023"
case 'MMM dd, yyyy':
// "Apr 29, 2023" (same as above, just different format string)
return d.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
Expand Down
Loading