@tanstack/create 0.61.5 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/config-file.js +5 -2
  3. package/dist/custom-add-ons/starter.js +45 -28
  4. package/dist/file-helpers.js +1 -0
  5. package/dist/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
  6. package/dist/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  7. package/dist/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
  8. package/dist/frameworks/react/add-ons/store/package.json +2 -2
  9. package/dist/frameworks/react/add-ons/strapi/README.md +158 -8
  10. package/dist/frameworks/react/add-ons/strapi/assets/_dot_env.local.append +1 -1
  11. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/block-renderer.tsx +55 -0
  12. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/index.ts +14 -0
  13. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/media.tsx +27 -0
  14. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/quote.tsx +19 -0
  15. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/rich-text.tsx +11 -0
  16. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/slider.tsx +28 -0
  17. package/dist/frameworks/react/add-ons/strapi/assets/src/components/markdown-content.tsx +74 -0
  18. package/dist/frameworks/react/add-ons/strapi/assets/src/components/pagination.tsx +120 -0
  19. package/dist/frameworks/react/add-ons/strapi/assets/src/components/search.tsx +35 -0
  20. package/dist/frameworks/react/add-ons/strapi/assets/src/components/strapi-image.tsx +47 -0
  21. package/dist/frameworks/react/add-ons/strapi/assets/src/data/loaders/articles.ts +106 -0
  22. package/dist/frameworks/react/add-ons/strapi/assets/src/data/loaders/index.ts +28 -0
  23. package/dist/frameworks/react/add-ons/strapi/assets/src/data/strapi-sdk.ts +9 -0
  24. package/dist/frameworks/react/add-ons/strapi/assets/src/lib/strapi-utils.ts +25 -0
  25. package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.$articleId.tsx +170 -0
  26. package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.tsx +269 -43
  27. package/dist/frameworks/react/add-ons/strapi/assets/src/types/strapi.ts +90 -0
  28. package/dist/frameworks/react/add-ons/strapi/info.json +3 -3
  29. package/dist/frameworks/react/add-ons/strapi/package.json +5 -2
  30. package/dist/frameworks/react/index.js +2 -2
  31. package/dist/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
  32. package/dist/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
  33. package/dist/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
  34. package/dist/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
  35. package/dist/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
  36. package/dist/frameworks/react/project/base/content-collections.ts.ejs +37 -0
  37. package/dist/frameworks/react/project/base/package.json +8 -1
  38. package/dist/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
  39. package/dist/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
  40. package/dist/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
  41. package/dist/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
  42. package/dist/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
  43. package/dist/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
  44. package/dist/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
  45. package/dist/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
  46. package/dist/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
  47. package/dist/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
  48. package/dist/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
  49. package/dist/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
  50. package/dist/frameworks/react/project/base/src/main.tsx.ejs +0 -1
  51. package/dist/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
  52. package/dist/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
  53. package/dist/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
  54. package/dist/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
  55. package/dist/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
  56. package/dist/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
  57. package/dist/frameworks/react/project/base/src/styles.css.ejs +268 -6
  58. package/dist/frameworks/react/project/base/tsconfig.json.ejs +2 -0
  59. package/dist/frameworks/react/project/base/vite.config.ts.ejs +2 -0
  60. package/dist/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  61. package/dist/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
  62. package/dist/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
  63. package/dist/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
  64. package/dist/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
  65. package/dist/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
  66. package/dist/frameworks.js +3 -0
  67. package/dist/package-json.js +1 -1
  68. package/dist/registry.js +21 -4
  69. package/dist/types/registry.d.ts +38 -0
  70. package/package.json +1 -1
  71. package/src/config-file.ts +6 -2
  72. package/src/custom-add-ons/starter.ts +30 -10
  73. package/src/file-helpers.ts +1 -0
  74. package/src/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
  75. package/src/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  76. package/src/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
  77. package/src/frameworks/react/add-ons/store/package.json +2 -2
  78. package/src/frameworks/react/add-ons/strapi/README.md +158 -8
  79. package/src/frameworks/react/add-ons/strapi/assets/_dot_env.local.append +1 -1
  80. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/block-renderer.tsx +55 -0
  81. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/index.ts +14 -0
  82. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/media.tsx +27 -0
  83. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/quote.tsx +19 -0
  84. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/rich-text.tsx +11 -0
  85. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/slider.tsx +28 -0
  86. package/src/frameworks/react/add-ons/strapi/assets/src/components/markdown-content.tsx +74 -0
  87. package/src/frameworks/react/add-ons/strapi/assets/src/components/pagination.tsx +120 -0
  88. package/src/frameworks/react/add-ons/strapi/assets/src/components/search.tsx +35 -0
  89. package/src/frameworks/react/add-ons/strapi/assets/src/components/strapi-image.tsx +47 -0
  90. package/src/frameworks/react/add-ons/strapi/assets/src/data/loaders/articles.ts +106 -0
  91. package/src/frameworks/react/add-ons/strapi/assets/src/data/loaders/index.ts +28 -0
  92. package/src/frameworks/react/add-ons/strapi/assets/src/data/strapi-sdk.ts +9 -0
  93. package/src/frameworks/react/add-ons/strapi/assets/src/lib/strapi-utils.ts +25 -0
  94. package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.$articleId.tsx +170 -0
  95. package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.tsx +269 -43
  96. package/src/frameworks/react/add-ons/strapi/assets/src/types/strapi.ts +90 -0
  97. package/src/frameworks/react/add-ons/strapi/info.json +3 -3
  98. package/src/frameworks/react/add-ons/strapi/package.json +5 -2
  99. package/src/frameworks/react/index.ts +2 -2
  100. package/src/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
  101. package/src/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
  102. package/src/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
  103. package/src/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
  104. package/src/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
  105. package/src/frameworks/react/project/base/content-collections.ts.ejs +37 -0
  106. package/src/frameworks/react/project/base/package.json +8 -1
  107. package/src/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
  108. package/src/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
  109. package/src/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
  110. package/src/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
  111. package/src/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
  112. package/src/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
  113. package/src/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
  114. package/src/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
  115. package/src/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
  116. package/src/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
  117. package/src/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
  118. package/src/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
  119. package/src/frameworks/react/project/base/src/main.tsx.ejs +0 -1
  120. package/src/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
  121. package/src/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
  122. package/src/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
  123. package/src/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
  124. package/src/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
  125. package/src/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
  126. package/src/frameworks/react/project/base/src/styles.css.ejs +268 -6
  127. package/src/frameworks/react/project/base/tsconfig.json.ejs +2 -0
  128. package/src/frameworks/react/project/base/vite.config.ts.ejs +2 -0
  129. package/src/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
  130. package/src/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
  131. package/src/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
  132. package/src/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
  133. package/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
  134. package/src/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
  135. package/src/frameworks.ts +4 -0
  136. package/src/package-json.ts +1 -1
  137. package/src/registry.ts +28 -4
  138. package/tests/add-ons.test.ts +4 -4
  139. package/tests/config-file.test.ts +3 -3
  140. package/tests/custom-add-ons/starter.test.ts +34 -2
  141. package/tests/frameworks.test.ts +24 -0
  142. package/tests/options.test.ts +4 -4
  143. package/tests/utils.test.ts +2 -2
  144. package/dist/frameworks/react/add-ons/strapi/assets/src/lib/strapiClient.ts +0 -7
  145. package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi_.$articleId.tsx +0 -78
  146. package/src/frameworks/react/add-ons/strapi/assets/src/lib/strapiClient.ts +0 -7
  147. package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi_.$articleId.tsx +0 -78
@@ -0,0 +1,23 @@
1
+ export function MdxMetrics({
2
+ items,
3
+ }: {
4
+ items: Array<{ label: string; value: string }>
5
+ }) {
6
+ return (
7
+ <div className="not-prose my-6 grid gap-3 sm:grid-cols-3">
8
+ {items.map((item) => (
9
+ <div
10
+ key={item.label}
11
+ className="rounded-xl border border-[var(--line)] bg-[var(--chip-bg)] px-4 py-3"
12
+ >
13
+ <p className="m-0 text-xs uppercase tracking-[0.12em] text-[var(--sea-ink-soft)]">
14
+ {item.label}
15
+ </p>
16
+ <p className="m-0 mt-1 text-lg font-semibold text-[var(--sea-ink)]">
17
+ {item.value}
18
+ </p>
19
+ </div>
20
+ ))}
21
+ </div>
22
+ )
23
+ }
@@ -0,0 +1,81 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ type ThemeMode = 'light' | 'dark' | 'auto'
4
+
5
+ function getInitialMode(): ThemeMode {
6
+ if (typeof window === 'undefined') {
7
+ return 'auto'
8
+ }
9
+
10
+ const stored = window.localStorage.getItem('theme')
11
+ if (stored === 'light' || stored === 'dark' || stored === 'auto') {
12
+ return stored
13
+ }
14
+
15
+ return 'auto'
16
+ }
17
+
18
+ function applyThemeMode(mode: ThemeMode) {
19
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
20
+ const resolved = mode === 'auto' ? (prefersDark ? 'dark' : 'light') : mode
21
+
22
+ document.documentElement.classList.remove('light', 'dark')
23
+ document.documentElement.classList.add(resolved)
24
+
25
+ if (mode === 'auto') {
26
+ document.documentElement.removeAttribute('data-theme')
27
+ } else {
28
+ document.documentElement.setAttribute('data-theme', mode)
29
+ }
30
+
31
+ document.documentElement.style.colorScheme = resolved
32
+ }
33
+
34
+ export default function ThemeToggle() {
35
+ const [mode, setMode] = useState<ThemeMode>('auto')
36
+
37
+ useEffect(() => {
38
+ const initialMode = getInitialMode()
39
+ setMode(initialMode)
40
+ applyThemeMode(initialMode)
41
+ }, [])
42
+
43
+ useEffect(() => {
44
+ if (mode !== 'auto') {
45
+ return
46
+ }
47
+
48
+ const media = window.matchMedia('(prefers-color-scheme: dark)')
49
+ const onChange = () => applyThemeMode('auto')
50
+
51
+ media.addEventListener('change', onChange)
52
+ return () => {
53
+ media.removeEventListener('change', onChange)
54
+ }
55
+ }, [mode])
56
+
57
+ function toggleMode() {
58
+ const nextMode: ThemeMode =
59
+ mode === 'light' ? 'dark' : mode === 'dark' ? 'auto' : 'light'
60
+ setMode(nextMode)
61
+ applyThemeMode(nextMode)
62
+ window.localStorage.setItem('theme', nextMode)
63
+ }
64
+
65
+ const label =
66
+ mode === 'auto'
67
+ ? 'Theme mode: auto (system). Click to switch to light mode.'
68
+ : `Theme mode: ${mode}. Click to switch mode.`
69
+
70
+ return (
71
+ <button
72
+ type="button"
73
+ onClick={toggleMode}
74
+ aria-label={label}
75
+ title={label}
76
+ className="rounded-full border border-[var(--chip-line)] bg-[var(--chip-bg)] px-3 py-1.5 text-sm font-semibold text-[var(--sea-ink)] shadow-[0_8px_22px_rgba(30,90,72,0.08)] transition hover:-translate-y-0.5"
77
+ >
78
+ {mode === 'auto' ? 'Auto' : mode === 'dark' ? 'Dark' : 'Light'}
79
+ </button>
80
+ )
81
+ }
@@ -0,0 +1,4 @@
1
+ export const SITE_TITLE = 'TanStack Start'
2
+ export const SITE_DESCRIPTION =
3
+ 'A tropical, breathable app starter with full-document SSR, server functions, streaming, and type-safe routing.'
4
+ export const SITE_URL = 'https://example.com'
@@ -1,5 +1,4 @@
1
1
  <% if (!routerOnly) { ignoreFile() } %>
2
- import React from 'react'
3
2
  import ReactDOM from 'react-dom/client'
4
3
  import { RouterProvider, createRouter } from '@tanstack/react-router'
5
4
  import { routeTree } from './routeTree.gen'
@@ -33,9 +33,9 @@ import {
33
33
  HeadContent, Scripts, <% if (hasContext) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router'
34
34
  import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools';
35
35
  import { TanStackDevtools } from '@tanstack/react-devtools'
36
- <% if (addOns.length) { %>
36
+ import Footer from '../components/Footer'
37
37
  import Header from '../components/Header'
38
- <% } %><% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider' || i.type === 'devtools')) { %>
38
+ <% for(const integration of integrations.filter(i => i.type === 'layout' || i.type === 'provider' || i.type === 'devtools')) { %>
39
39
  import <%= integration.jsName %> from '<%= relativePath(integration.path, true) %>'
40
40
  <% } %><% if (addOnEnabled.paraglide) { %>
41
41
  import { getLocale } from '#/paraglide/runtime'
@@ -61,6 +61,8 @@ interface MyRouterContext <% if (addOnEnabled["apollo-client"]) {%> extends Apol
61
61
  <% } %>
62
62
  }<% } %>
63
63
 
64
+ const THEME_INIT_SCRIPT = `(function(){try{var stored=window.localStorage.getItem('theme');var mode=(stored==='light'||stored==='dark'||stored==='auto')?stored:'auto';var prefersDark=window.matchMedia('(prefers-color-scheme: dark)').matches;var resolved=mode==='auto'?(prefersDark?'dark':'light'):mode;var root=document.documentElement;root.classList.remove('light','dark');root.classList.add(resolved);if(mode==='auto'){root.removeAttribute('data-theme')}else{root.setAttribute('data-theme',mode)}root.style.colorScheme=resolved;}catch(e){}})();`
65
+
64
66
  export const Route = <% if (hasContext) { %>createRootRouteWithContext<MyRouterContext>()<% } else { %>createRootRoute<% } %>({
65
67
  <% if (addOnEnabled.paraglide) { %>
66
68
  beforeLoad: async () => {
@@ -96,14 +98,16 @@ export const Route = <% if (hasContext) { %>createRootRouteWithContext<MyRouterC
96
98
 
97
99
  function RootDocument({ children }: { children: React.ReactNode }) {
98
100
  return (
99
- <% if (addOnEnabled.paraglide) { %><html lang={getLocale()}><% } else { %><html lang="en"><% } %>
101
+ <% if (addOnEnabled.paraglide) { %><html lang={getLocale()} suppressHydrationWarning><% } else { %><html lang="en" suppressHydrationWarning><% } %>
100
102
  <head>
103
+ <script dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }} />
101
104
  <HeadContent />
102
105
  </head>
103
- <body>
106
+ <body className="font-sans antialiased [overflow-wrap:anywhere] selection:bg-[rgba(79,184,178,0.24)]">
104
107
  <% for(const integration of integrations.filter(i => i.type === 'provider')) { %><<%= integration.jsName %>>
105
- <% } %><% if (addOns.length) { %><Header />
106
- <% } %>{children}
108
+ <% } %><Header />
109
+ {children}
110
+ <Footer />
107
111
  <TanStackDevtools
108
112
  config={{
109
113
  position: 'bottom-right',
@@ -0,0 +1,27 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+
3
+ export const Route = createFileRoute('/about')({
4
+ component: About,
5
+ })
6
+
7
+ function About() {
8
+ return (
9
+ <main className="page-wrap px-4 py-12">
10
+ <section className="island-shell rounded-2xl p-6 sm:p-8">
11
+ <img
12
+ src="/images/lagoon-about.svg"
13
+ alt=""
14
+ className="mb-6 h-56 w-full rounded-2xl object-cover"
15
+ />
16
+ <p className="island-kicker mb-2">About</p>
17
+ <h1 className="display-title mb-3 text-4xl font-bold text-[var(--sea-ink)] sm:text-5xl">
18
+ Built for shipping fast.
19
+ </h1>
20
+ <p className="m-0 max-w-3xl text-base leading-8 text-[var(--sea-ink-soft)]">
21
+ TanStack Start gives you type-safe routing, server functions, and modern SSR
22
+ defaults so you can focus on product work instead of framework glue.
23
+ </p>
24
+ </section>
25
+ </main>
26
+ )
27
+ }
@@ -0,0 +1,71 @@
1
+ import { createFileRoute, notFound } from '@tanstack/react-router'
2
+ import { MDXContent } from '@content-collections/mdx/react'
3
+ import { allBlogs } from 'content-collections'
4
+ import { SITE_URL } from '#/lib/site'
5
+ import { MdxCallout } from '#/components/MdxCallout'
6
+ import { MdxMetrics } from '#/components/MdxMetrics'
7
+
8
+ export const Route = createFileRoute('/blog/$slug')({
9
+ loader: ({ params }) => {
10
+ const post = Array.from(
11
+ new Map(
12
+ [...allBlogs]
13
+ .sort((a, b) => new Date(b.pubDate).valueOf() - new Date(a.pubDate).valueOf())
14
+ .map((entry) => [entry.slug, entry]),
15
+ ).values(),
16
+ ).find((entry) => entry.slug === params.slug)
17
+ if (!post) throw notFound()
18
+ return post
19
+ },
20
+ head: ({ loaderData, params }) => {
21
+ const title = loaderData?.title ?? 'Post'
22
+ const description = loaderData?.description ?? ''
23
+ const image = loaderData?.heroImage ?? '/images/lagoon-1.svg'
24
+ return {
25
+ links: [{ rel: 'canonical', href: `${SITE_URL}/blog/${params.slug}` }],
26
+ meta: [
27
+ { title },
28
+ { name: 'description', content: description },
29
+ {
30
+ property: 'og:image',
31
+ content: image.startsWith('http') ? image : `${SITE_URL}${image}`,
32
+ },
33
+ ],
34
+ }
35
+ },
36
+ component: BlogPost,
37
+ })
38
+
39
+ function BlogPost() {
40
+ const post = Route.useLoaderData()
41
+
42
+ return (
43
+ <main className="page-wrap px-4 pb-12 pt-16">
44
+ <article className="island-shell rounded-2xl p-6 sm:p-8">
45
+ {post.heroImage ? (
46
+ <img
47
+ src={post.heroImage}
48
+ alt=""
49
+ className="mb-6 h-64 w-full rounded-2xl object-cover"
50
+ />
51
+ ) : null}
52
+ <p className="island-kicker mb-2">Post</p>
53
+ <h1 className="display-title mb-3 text-4xl font-bold text-[var(--sea-ink)] sm:text-5xl">
54
+ {post.title}
55
+ </h1>
56
+ <p className="mb-6 text-sm text-[var(--sea-ink-soft)]">
57
+ {new Date(post.pubDate).toLocaleDateString()}
58
+ </p>
59
+ <div
60
+ className="prose prose-slate prose-headings:text-[var(--sea-ink)] prose-p:text-[var(--sea-ink-soft)] prose-li:text-[var(--sea-ink-soft)] prose-ul:text-[var(--sea-ink-soft)] prose-ol:text-[var(--sea-ink-soft)] prose-strong:text-[var(--sea-ink)] prose-a:text-[var(--lagoon-deep)] max-w-none"
61
+ >
62
+ {post.mdx ? (
63
+ <MDXContent code={post.mdx} components={{ MdxCallout, MdxMetrics }} />
64
+ ) : (
65
+ <div dangerouslySetInnerHTML={{ __html: post.html ?? '' }} />
66
+ )}
67
+ </div>
68
+ </article>
69
+ </main>
70
+ )
71
+ }
@@ -0,0 +1,93 @@
1
+ import { Link, createFileRoute } from '@tanstack/react-router'
2
+ import { allBlogs } from 'content-collections'
3
+ import { SITE_DESCRIPTION, SITE_TITLE, SITE_URL } from '#/lib/site'
4
+
5
+ const canonical = `${SITE_URL}/blog`
6
+ const pageTitle = `Blog | ${SITE_TITLE}`
7
+
8
+ export const Route = createFileRoute('/blog/')({
9
+ head: () => ({
10
+ links: [{ rel: 'canonical', href: canonical }],
11
+ meta: [
12
+ { title: pageTitle },
13
+ { name: 'description', content: SITE_DESCRIPTION },
14
+ { property: 'og:image', content: `${SITE_URL}/images/lagoon-1.svg` },
15
+ ],
16
+ }),
17
+ component: BlogIndex,
18
+ })
19
+
20
+ function BlogIndex() {
21
+ const postsByDate = Array.from(
22
+ new Map(
23
+ [...allBlogs]
24
+ .sort((a, b) => new Date(b.pubDate).valueOf() - new Date(a.pubDate).valueOf())
25
+ .map((post) => [post.slug, post]),
26
+ ).values(),
27
+ )
28
+
29
+ const featured = postsByDate[0]
30
+ const posts = postsByDate.slice(1)
31
+ return (
32
+ <main className="page-wrap px-4 pb-8 pt-14">
33
+ <section className="mb-4">
34
+ <p className="island-kicker mb-2">Latest Dispatches</p>
35
+ <h1 className="display-title m-0 text-4xl font-bold tracking-tight text-[var(--sea-ink)] sm:text-5xl">
36
+ Blog
37
+ </h1>
38
+ </section>
39
+
40
+ <section className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
41
+ <article className="island-shell rise-in rounded-2xl p-5 sm:p-6 lg:col-span-2">
42
+ {featured.heroImage ? (
43
+ <img
44
+ src={featured.heroImage}
45
+ alt=""
46
+ className="mb-4 h-44 w-full rounded-xl object-cover xl:h-60"
47
+ />
48
+ ) : null}
49
+ <h2 className="m-0 text-2xl font-semibold text-[var(--sea-ink)]">
50
+ <Link
51
+ to="/blog/$slug"
52
+ params={{ slug: featured.slug }}
53
+ className="no-underline"
54
+ >
55
+ {featured.title}
56
+ </Link>
57
+ </h2>
58
+ <p className="mb-2 mt-3 text-base text-[var(--sea-ink-soft)]">
59
+ {featured.description}
60
+ </p>
61
+ <p className="m-0 text-xs text-[var(--sea-ink-soft)]">
62
+ {new Date(featured.pubDate).toLocaleDateString()}
63
+ </p>
64
+ </article>
65
+
66
+ {posts.map((post, index) => (
67
+ <article
68
+ key={post.slug}
69
+ className="island-shell rise-in rounded-2xl p-5 sm:last:col-span-2 lg:last:col-span-1"
70
+ style={{ animationDelay: `${index * 80 + 120}ms` }}
71
+ >
72
+ {post.heroImage ? (
73
+ <img
74
+ src={post.heroImage}
75
+ alt=""
76
+ className="mb-4 h-44 w-full rounded-xl object-cover"
77
+ />
78
+ ) : null}
79
+ <h2 className="m-0 text-2xl font-semibold text-[var(--sea-ink)]">
80
+ <Link to="/blog/$slug" params={{ slug: post.slug }} className="no-underline">
81
+ {post.title}
82
+ </Link>
83
+ </h2>
84
+ <p className="mb-2 mt-2 text-sm text-[var(--sea-ink-soft)]">{post.description}</p>
85
+ <p className="m-0 text-xs text-[var(--sea-ink-soft)]">
86
+ {new Date(post.pubDate).toLocaleDateString()}
87
+ </p>
88
+ </article>
89
+ ))}
90
+ </section>
91
+ </main>
92
+ )
93
+ }
@@ -1,105 +1,72 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
- import {
3
- Zap, Server, Route as RouteIcon, Shield, Waves, Sparkles,
4
- } from "lucide-react";
5
2
 
6
3
  export const Route = createFileRoute("/")({ component: App });
7
4
 
8
5
  function App() {
9
- const features = [
10
- {
11
- icon: <Zap className="w-12 h-12 text-cyan-400" />,
12
- title: "Powerful Server Functions",
13
- description: "Write server-side code that seamlessly integrates with your client components. Type-safe, secure, and simple.",
14
- },
15
- {
16
- icon: <Server className="w-12 h-12 text-cyan-400" />,
17
- title: "Flexible Server Side Rendering",
18
- description: "Full-document SSR, streaming, and progressive enhancement out of the box. Control exactly what renders where.",
19
- },
20
- {
21
- icon: <RouteIcon className="w-12 h-12 text-cyan-400" />,
22
- title: "API Routes",
23
- description: "Build type-safe API endpoints alongside your application. No separate backend needed.",
24
- },
25
- {
26
- icon: <Shield className="w-12 h-12 text-cyan-400" />,
27
- title: "Strongly Typed Everything",
28
- description: "End-to-end type safety from server to client. Catch errors before they reach production.",
29
- },
30
- {
31
- icon: <Waves className="w-12 h-12 text-cyan-400" />,
32
- title: "Full Streaming Support",
33
- description: "Stream data from server to client progressively. Perfect for AI applications and real-time updates.",
34
- },
35
- {
36
- icon: <Sparkles className="w-12 h-12 text-cyan-400" />,
37
- title: "Next Generation Ready",
38
- description: "Built from the ground up for modern web applications. Deploy anywhere JavaScript runs.",
39
- },
40
- ];
41
-
42
6
  return (
43
- <div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900">
44
- <section className="relative py-20 px-6 text-center overflow-hidden">
45
- <div className="absolute inset-0 bg-gradient-to-r from-cyan-500/10 via-blue-500/10 to-purple-500/10"></div>
46
- <div className="relative max-w-5xl mx-auto">
47
- <div className="flex items-center justify-center gap-6 mb-6">
48
- <img
49
- src="/tanstack-circle-logo.png"
50
- alt="TanStack Logo"
51
- className="w-24 h-24 md:w-32 md:h-32"
52
- />
53
- <h1 className="text-6xl md:text-7xl font-black text-white [letter-spacing:-0.08em]">
54
- <span className="text-gray-300">TANSTACK</span>{" "}
55
- <span className="bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">
56
- START
57
- </span>
58
- </h1>
59
- </div>
60
- <p className="text-2xl md:text-3xl text-gray-300 mb-4 font-light">
61
- The framework for next generation AI applications
62
- </p>
63
- <p className="text-lg text-gray-400 max-w-3xl mx-auto mb-8">
64
- Full-stack framework powered by TanStack Router for React and Solid.
65
- Build modern applications with server functions, streaming, and type
66
- safety.
67
- </p>
68
- <div className="flex flex-col items-center gap-4">
69
- <a
70
- href="https://tanstack.com/start"
71
- target="_blank"
72
- rel="noopener noreferrer"
73
- className="px-8 py-3 bg-cyan-500 hover:bg-cyan-600 text-white font-semibold rounded-lg transition-colors shadow-lg shadow-cyan-500/50"
74
- >
75
- Documentation
76
- </a>
77
- <p className="text-gray-400 text-sm mt-2">
78
- Begin your TanStack Start journey by editing{" "}
79
- <code className="px-2 py-1 bg-slate-700 rounded text-cyan-400">
80
- /src/routes/index.tsx
81
- </code>
82
- </p>
83
- </div>
7
+ <main className="page-wrap px-4 pb-8 pt-14">
8
+ <section className="island-shell rise-in relative overflow-hidden rounded-[2rem] px-6 py-10 sm:px-10 sm:py-14">
9
+ <div className="pointer-events-none absolute -left-20 -top-24 h-56 w-56 rounded-full bg-[radial-gradient(circle,rgba(79,184,178,0.32),transparent_66%)]" />
10
+ <div className="pointer-events-none absolute -bottom-20 -right-20 h-56 w-56 rounded-full bg-[radial-gradient(circle,rgba(47,106,74,0.18),transparent_66%)]" />
11
+ <p className="island-kicker mb-3">TanStack Start Base Template</p>
12
+ <h1 className="display-title mb-5 max-w-3xl text-4xl leading-[1.02] font-bold tracking-tight text-[var(--sea-ink)] sm:text-6xl">
13
+ Island hours, but for product teams.
14
+ </h1>
15
+ <p className="mb-8 max-w-2xl text-base text-[var(--sea-ink-soft)] sm:text-lg">
16
+ A tropical, breathable app starter with full-document SSR, server functions,
17
+ streaming, and type-safe routing. Calm on the eyes. Fast in production.
18
+ </p>
19
+ <div className="flex flex-wrap gap-3">
20
+ <a
21
+ href="/blog"
22
+ className="rounded-full border border-[rgba(50,143,151,0.3)] bg-[rgba(79,184,178,0.14)] px-5 py-2.5 text-sm font-semibold text-[var(--lagoon-deep)] no-underline transition hover:-translate-y-0.5 hover:bg-[rgba(79,184,178,0.24)]"
23
+ >
24
+ Explore Posts
25
+ </a>
26
+ <a
27
+ href="https://tanstack.com/router"
28
+ target="_blank"
29
+ rel="noopener noreferrer"
30
+ className="rounded-full border border-[rgba(23,58,64,0.2)] bg-white/50 px-5 py-2.5 text-sm font-semibold text-[var(--sea-ink)] no-underline transition hover:-translate-y-0.5 hover:border-[rgba(23,58,64,0.35)]"
31
+ >
32
+ Router Guide
33
+ </a>
84
34
  </div>
85
35
  </section>
86
36
 
87
- <section className="py-16 px-6 max-w-7xl mx-auto">
88
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
89
- {features.map((feature, index) => (
90
- <div
91
- key={index}
92
- className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-6 hover:border-cyan-500/50 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-500/10"
37
+ <section className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
38
+ {[
39
+ ['Type-Safe Routing', 'Routes and links stay in sync across every page.'],
40
+ ['Server Functions', 'Call server code from your UI without creating API boilerplate.'],
41
+ ['Streaming by Default', 'Ship progressively rendered responses for faster experiences.'],
42
+ ['Tailwind Native', 'Design quickly with utility-first styling and custom tokens.'],
43
+ ].map(([title, desc], index) => (
44
+ <article
45
+ key={title}
46
+ className="island-shell feature-card rise-in rounded-2xl p-5"
47
+ style={{ animationDelay: `${index * 90 + 80}ms` }}
93
48
  >
94
- <div className="mb-4">{feature.icon}</div>
95
- <h3 className="text-xl font-semibold text-white mb-3">
96
- {feature.title}
97
- </h3>
98
- <p className="text-gray-400 leading-relaxed">{feature.description}</p>
99
- </div>
49
+ <h2 className="mb-2 text-base font-semibold text-[var(--sea-ink)]">{title}</h2>
50
+ <p className="m-0 text-sm text-[var(--sea-ink-soft)]">{desc}</p>
51
+ </article>
100
52
  ))}
101
- </div>
102
53
  </section>
103
- </div>
54
+
55
+ <section className="island-shell mt-8 rounded-2xl p-6">
56
+ <p className="island-kicker mb-2">Quick Start</p>
57
+ <ul className="m-0 list-disc space-y-2 pl-5 text-sm text-[var(--sea-ink-soft)]">
58
+ <li>
59
+ Edit <code>src/routes/index.tsx</code> to customize the hero and product narrative.
60
+ </li>
61
+ <li>
62
+ Update <code>src/components/Header.tsx</code> and <code>src/components/Footer.tsx</code>{' '}
63
+ for brand links.
64
+ </li>
65
+ <li>
66
+ Add routes in <code>src/routes</code> and tweak visual tokens in <code>src/styles.css</code>.
67
+ </li>
68
+ </ul>
69
+ </section>
70
+ </main>
104
71
  );
105
72
  }
@@ -0,0 +1,35 @@
1
+ <% if (routerOnly) { ignoreFile() } %>
2
+ import { createFileRoute } from '@tanstack/react-router'
3
+ import { allBlogs } from 'content-collections'
4
+ import { SITE_DESCRIPTION, SITE_TITLE, SITE_URL } from '#/lib/site'
5
+
6
+ export const Route = createFileRoute('/rss.xml')({
7
+ server: {
8
+ handlers: {
9
+ GET: () => {
10
+ const posts = Array.from(
11
+ new Map(
12
+ [...allBlogs]
13
+ .sort((a, b) => new Date(b.pubDate).valueOf() - new Date(a.pubDate).valueOf())
14
+ .map((post) => [post.slug, post]),
15
+ ).values(),
16
+ )
17
+
18
+ const items = posts
19
+ .map((post) => {
20
+ const url = `${SITE_URL}/blog/${post.slug}`
21
+ return `<item><title><![CDATA[${post.title}]]></title><description><![CDATA[${post.description}]]></description><link>${url}</link><guid>${url}</guid><pubDate>${new Date(post.pubDate).toUTCString()}</pubDate></item>`
22
+ })
23
+ .join('')
24
+
25
+ const xml = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title><![CDATA[${SITE_TITLE}]]></title><description><![CDATA[${SITE_DESCRIPTION}]]></description><link>${SITE_URL}</link>${items}</channel></rss>`
26
+
27
+ return new Response(xml, {
28
+ headers: {
29
+ 'Content-Type': 'application/rss+xml; charset=utf-8',
30
+ },
31
+ })
32
+ },
33
+ },
34
+ },
35
+ })