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
149 changes: 149 additions & 0 deletions src/blog/tanstack-start-1-0.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/components/PartnersGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type PartnerItem = (typeof allPartners)[number]
type PartnersGridProps = {
analyticsPlacement?: PartnerPlacement
partnersList?: PartnerItem[]
status?: PartnerItem['status']
/**
* Optional final band rendered inside the same bordered container as the tier
* bands — used to fold the OSS Sponsors band into the stack.
Expand Down Expand Up @@ -157,10 +158,11 @@ function PartnerGridItem({
export function PartnersGrid({
analyticsPlacement = 'grid',
partnersList,
status = 'active',
trailingBand,
}: PartnersGridProps) {
const items = (partnersList ?? allPartners).filter(
(partner) => partner.status === 'active',
(partner) => partner.status === status,
)

const placementContext = usePartnerPlacementContext({
Expand Down
10 changes: 9 additions & 1 deletion src/components/markdown/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
} from '~/utils/markdown'
import { isSafeHttpUrl } from '~/utils/url-boundary'
import { mapStartHostingPartnerElements } from '~/utils/start-hosting-guide'
import { mapMarkdownPartnerLists } from '~/utils/markdown-partner-lists'
import { PartnerList, PreviousPartnerList } from './PartnerList'
import { StartPartnerContributions } from './StartPartnerContributions'
import { CodeBlock } from './CodeBlock'
import { LiveExample } from './LiveExample'
import { MarkdownLink } from './MarkdownLink'
Expand Down Expand Up @@ -54,7 +57,9 @@ export function Markdown({
[content, document],
)
const renderDocument = React.useMemo(() => {
const withHostingPartners = mapStartHostingPartnerElements(parsed)
const withHostingPartners = mapStartHostingPartnerElements(
mapMarkdownPartnerLists(parsed),
)

return chartEmbedSource
? mapChartsCatalogEmbeds(withHostingPartners, chartEmbedSource)
Expand Down Expand Up @@ -257,6 +262,9 @@ function createMarkdownComponents(
'md-live-example': LiveExample,
'md-tab-panel': MdTabPanel,
pre: CodeBlock,
'start-partner-contributions': StartPartnerContributions,
'partner-list': PartnerList,
'previous-partner-list': PreviousPartnerList,
'start-hosting-lovable-logo': StartHostingLovableLogo,
'start-hosting-partners': StartHostingPartners,
'start-hosting-render-logo': StartHostingRenderLogo,
Expand Down
18 changes: 18 additions & 0 deletions src/components/markdown/PartnerList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PartnersGrid } from '~/components/PartnersGrid'
import type { Partner } from '~/utils/partners'

export function PartnerList({
status = 'active',
}: {
status?: Partner['status']
}) {
return (
<div className="not-prose my-8">
<PartnersGrid status={status} />
</div>
)
}

export function PreviousPartnerList() {
return <PartnerList status="inactive" />
}
120 changes: 120 additions & 0 deletions src/components/markdown/StartPartnerContributions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { partners, partnerTierLabels } from '~/utils/partners'
import { getPartnerTierGroupsForPlacement } from '~/utils/partner-placement'
import { usePartnerPlacementContext } from '~/utils/usePartnerPlacementContext'

const contributions: Record<string, { href: string; description: string }> = {
clerk: {
href: 'https://clerk.com/docs/reference/tanstack-react-start/overview',
description:
'built a dedicated TanStack Start SDK, with authentication components and helpers for both the client and server.',
},
workos: {
href: 'https://workos.com/docs/sdks/authkit-tanstack-start',
description:
'built an AuthKit package for TanStack Start, with authentication, session management, and route protection.',
},
'ag-grid': {
href: 'https://tanstack.com/blog/ag-grid-partnership',
description:
'has supported TanStack for years through its Table partnership, helping fund the broader open-source work we do.',
},
serpapi: {
href: 'https://github.com/serpapi/local-llm-web-search',
description:
'published a Start application that gives a local language model web search, with the source available to build on.',
},
openrouter: {
href: 'https://tanstack.com/blog/openrouter-partnership',
description:
'supports the wider TanStack ecosystem through sponsorship and our work together on AI tooling.',
},
sentry: {
href: 'https://github.com/getsentry/sentry-javascript/tree/develop/packages/tanstackstart-react',
description:
'built a dedicated TanStack Start SDK to help developers monitor errors and performance in their applications.',
},
prisma: {
href: 'https://docs.prisma.io/docs/guides/frameworks/tanstack-start',
description:
'created Start scaffolding and a database guide that connects Prisma to server functions and route loaders.',
},
electric: {
href: 'https://electric.ax/blog/2025/07/29/super-fast-apps-on-sync-with-tanstack-db',
description:
'published examples of building with Start, TanStack DB, and Electric sync to keep application data up to date.',
},
unkey: {
href: 'https://github.com/unkeyed/unkey/blob/d00fc71f507640eb1cdddef4ca7f8eda773a8cd9/web/apps/portal/package.json',
description:
'adopted Start, Router, and Query in its open-source portal, putting the stack to work in its own codebase.',
},
cloudflare: {
href: 'https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack-start/',
description:
'built a path to running Start on Workers, with a project generator and Vite integration.',
},
coderabbit: {
href: 'https://github.com/TanStack/router/pull/6996',
description:
'helps review changes in the Router and Start repo, including catching cookie-handling issues in the preview server.',
},
lovable: {
href: 'https://lovable.dev/blog/building-apps-using-tanstack-start',
description:
'chose Start to power newly generated applications, bringing server rendering and typed server functions to its users.',
},
netlify: {
href: 'https://app.netlify.com/start',
description:
'backed Start development and made it the default for its app builder, where you describe what you want and an agent builds it.',
},
railway: {
href: 'https://railway.com/deploy/tanstack-start',
description:
'published a Start deployment template so developers can get a full-stack application running on Railway.',
},
render: {
href: 'https://render.com/templates/tanstack-start',
description:
'built a Start deployment template with server rendering, server functions, and health checks already wired up.',
},
vercel: {
href: 'https://vercel.com/docs/frameworks/full-stack/tanstack-start',
description:
'added Start deployment support through Nitro, with a starter template and automatic scaling on Fluid compute.',
},
}

export function StartPartnerContributions() {
const context = usePartnerPlacementContext({
orderStrategy: 'tier-rotated',
surface: 'start-1-0-contributions',
})
const partnerGroups = getPartnerTierGroupsForPlacement(
partners.flatMap((partner) => {
const contribution = contributions[partner.id]
return partner.status === 'active' && contribution
? [{ ...partner, contribution }]
: []
}),
context,
)

return (
<div>
{partnerGroups.map(({ tier, partners: tierPartners }) => (
<section key={tier}>
<h3>{partnerTierLabels[tier]}</h3>
<ul>
{tierPartners.map(({ id, name, contribution }) => (
<li key={id}>
<a href={contribution.href}>{name}</a>{' '}
{contribution.description}
</li>
))}
</ul>
</section>
))}
</div>
)
}
27 changes: 27 additions & 0 deletions src/utils/markdown-partner-lists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { MarkdownDocument } from '@tanstack/markdown'

// Standalone blocks only, so examples inside code fences stay literal.
export function mapMarkdownPartnerLists(
document: MarkdownDocument,
): MarkdownDocument {
return {
...document,
children: document.children.map((block) => {
if (block.type !== 'html') return block

const match = block.value.match(
/^\s*<(partner-list|previous-partner-list|start-partner-contributions)>\s*<\/\1>\s*$/,
)
if (!match) return block

return {
type: 'component',
name: match[1],
tagName: match[1],
attributes: {},
properties: {},
children: [],
}
}),
}
}
40 changes: 40 additions & 0 deletions tests/markdown-partner-lists.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import assert from 'node:assert/strict'
import { it } from 'node:test'
import { parseSiteMarkdown } from '../src/utils/markdown'
import { mapMarkdownPartnerLists } from '../src/utils/markdown-partner-lists'
import { getPartnerTierGroupsForPlacement } from '../src/utils/partner-placement'

it('renders partner markers but leaves code examples alone', () => {
const document = mapMarkdownPartnerLists(
parseSiteMarkdown(
'<partner-list></partner-list>\n\n<previous-partner-list></previous-partner-list>\n\n<start-partner-contributions></start-partner-contributions>\n\n```html\n<partner-list></partner-list>\n```',
),
)
assert.equal(document.children[0]?.type, 'component')
assert.equal(document.children[1]?.type, 'component')
assert.equal(document.children[2]?.type, 'component')
assert.equal(document.children[3]?.type, 'code')
})

it('keeps tier order and reproduces the server order from serialized context', () => {
const partners: Parameters<typeof getPartnerTierGroupsForPlacement>[0] = [
{ id: 'a', name: 'A', tier: 'bronze', category: 'deployment' },
{ id: 'b', name: 'B', tier: 'gold', category: 'deployment' },
{ id: 'c', name: 'C', tier: 'gold', category: 'deployment' },
{ id: 'd', name: 'D', tier: 'silver', category: 'deployment' },
]
const context: Parameters<typeof getPartnerTierGroupsForPlacement>[1] = {
orderStrategy: 'tier-rotated',
rotationSeed: 'root-loader-seed',
}
const server = getPartnerTierGroupsForPlacement(partners, context)
const client = getPartnerTierGroupsForPlacement(
partners,
JSON.parse(JSON.stringify(context)),
)
assert.deepEqual(
server.map((group) => group.tier),
['gold', 'silver', 'bronze'],
)
assert.deepEqual(client, server)
})
Loading