diff --git a/content-collections.ts b/content-collections.ts index 772a71c61..27d4b8eba 100644 --- a/content-collections.ts +++ b/content-collections.ts @@ -34,6 +34,7 @@ const posts = defineCollection({ rss: z.boolean().optional(), excerpt: z.string(), authors: z.string().array(), + contributors: z.string().array().optional(), library: libraryListSchema.optional(), headerImage: z.string().optional(), headerVideo: z.string().optional(), diff --git a/src/blog/tanstack-start-1-0.md b/src/blog/tanstack-start-1-0.md index f75c4706e..b3596ea6d 100644 --- a/src/blog/tanstack-start-1-0.md +++ b/src/blog/tanstack-start-1-0.md @@ -1,22 +1,26 @@ --- -title: TanStack Start 1.0 +title: TanStack Start 1.0 is Stable published: 2026-10-13 draft: true excerpt: TanStack Start 1.0 is stable and ready for production. authors: - Tanner Linsley +contributors: + - Manuel Schiller + - Florian Pellet + - Christopher Horobin library: start +headerImage: https://media.tanstack.com/blog/tanstack-start-1-0/v11-title-poster.jpg +headerVideo: https://media.tanstack.com/blog/tanstack-start-1-0/v11.mp4 --- -# TanStack Start 1.0 - -_With contributions from [Manuel Schiller](https://github.com/schiller-manuel), [Florian Pellet](https://github.com/sheraff), and [Christopher Horobin](https://github.com/chorobin)._ +# TanStack Start 1.0 is Stable TanStack Start 1.0 is finally stable. And we're really excited to officially call it ready for production. I can't begin to say how proud I am of what we've built here and of the people who have built it. For a long time, I'd hear from people already running Start in production and have to remind them we were still in RC (that conversation happened a lot more than you'd think). I'm really glad we can finally drop that caveat. -We've had tons of people put Start into production through pretty much every stage of its development, from small projects and startups to entire enterprise teams and Fortune 500 companies. To our surprise, so many of these companies insisted on using it even when it was in beta. We'd explain where things were, and they'd basically tell us we're going to use it anyway. +We've had tons of people put Start into production through pretty much every stage of its development, from small projects and startups to entire enterprise teams and Fortune 500 companies. To our surprise, so many of these companies insisted on using it even when it was in beta. We'd explain where things were, and they'd basically tell us, "we're going to use it anyway." When enterprise teams are insisting on putting your beta software into production before it's even a release candidate, it's difficult to not feel the massive level of trust everybody is putting in you. So even though people have been successfully using Start in production for a long time, this is us making our formal commitment to the APIs we've built and the people building on them. You can now choose Start, learn it, ship something with it, and continue to build without wondering whether we're going to change the entire way your application works. diff --git a/src/routes/blog.$.tsx b/src/routes/blog.$.tsx index 2afb0010a..87608c97f 100644 --- a/src/routes/blog.$.tsx +++ b/src/routes/blog.$.tsx @@ -1,6 +1,7 @@ import { createFileRoute } from '@tanstack/react-router' import { PostNotFound } from './blog' import * as React from 'react' +import { PlayIcon } from '@phosphor-icons/react' import { MarkdownContent } from '~/components/markdown' import { Card } from '~/components/Card' import { LibrariesWidget } from '~/components/LibrariesWidget' @@ -12,6 +13,7 @@ import { useMediaQuery } from '~/utils/useMediaQuery' import { Toc } from '~/components/Toc' import { Breadcrumbs } from '~/components/Breadcrumbs' +import { findMaintainerByAuthorName } from '~/utils/authors' import { CoverFallback } from '~/components/CoverFallback' import { fetchBlogPost } from '~/utils/blog.functions' import { parseSiteMarkdown } from '~/utils/markdown' @@ -42,6 +44,9 @@ export const Route = createFileRoute('/blog/$')({ function BlogPost() { const { content, + authors, + contributors, + published, filePath, title, headerImage, @@ -50,7 +55,13 @@ function BlogPost() { library, } = Route.useLoaderData() const { _splat: slug } = Route.useParams() - const markdown = React.useMemo(() => parseSiteMarkdown(content), [content]) + const videoRef = React.useRef(null) + const [startedVideo, setStartedVideo] = React.useState() + const hasStartedVideo = startedVideo === headerVideo + const markdown = React.useMemo( + () => parseSiteMarkdown(content.replace(/^# [^\n]+\n*/m, '')), + [content], + ) const headings = markdown.headings const isTocVisible = headings.length > 1 @@ -146,23 +157,157 @@ function BlogPost() { ].join(' ')} >
- {headerVideo ? ( - + {new Intl.DateTimeFormat('en-US', { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric', + timeZone: 'UTC', + }).format(new Date(`${published}T00:00:00Z`))} + +

+ {title} +

+
+
+
+ Posted by +
+
+ {authors.map((name) => { + const author = findMaintainerByAuthorName(name) + return author ? ( + + +
+
+ {name} +
+
+ @{author.github} +
+
+
+ ) : ( + + {name} + + ) + })} +
+
+ {contributors.length ? ( +
+
+ With contributions from +
+
+
+ {contributors.map((name) => { + const person = + findMaintainerByAuthorName(name) + return person ? ( + + + + ) : null + })} +
+
+ {contributors.map((name, index) => { + const person = + findMaintainerByAuthorName(name) + return ( + + {index ? ', ' : ''} + {person ? ( + + {name} + + ) : ( + name + )} + + ) + })} +
+
+
+ ) : null} +
+ + {headerVideo ? ( +
+ + {!hasStartedVideo ? ( + + ) : null} +
) : null} {!headerImage && slug ? ( ) : null} heading.level === 1) - ? '' - : title - } + title="" markdown={markdown} preserveTabPanels repo={repo} diff --git a/src/utils/blog.functions.ts b/src/utils/blog.functions.ts index 9ea5f52cf..cb859c323 100644 --- a/src/utils/blog.functions.ts +++ b/src/utils/blog.functions.ts @@ -13,8 +13,6 @@ import { } from '~/utils/blog' import { type BlogCardPost, - formatAuthors, - formatPublishedDate, getBlogAuthorIdentities, getBlogLibraries, isBlogPostUnpublished, @@ -121,18 +119,13 @@ export const fetchBlogPost = createServerFn({ method: 'GET' }) ) const authors = normalizeBlogAuthors(post.authors) - const blogContent = `by ${formatAuthors(authors)} on ${formatPublishedDate( - post.published || '1970-01-01', - )}. - -${post.content}` - const isUnpublished = isBlogPostUnpublished(post) return { authorIdentities: getBlogAuthorIdentities(authors, allMaintainers), authors, - content: blogContent, + content: post.content, + contributors: post.contributors ?? [], description: post.excerpt, filePath: `src/blog/${data}.md`, headerImage: post.headerImage,