feat(create-app): add OpenGraph Image to template (#3023)

* feat(create-app): add OpenGraph Image to template

* format, add `ssr.external`

* add waku, fix template margin

* add next.config.mjs patch

* rewrite takumi docs page

* fix webp suffix

* sync create-app

* sync all `getPageImage`

* remove waku `rollupOptions`

* sync

* format

* fix meta property

* fix typo

* create `fumadocs-ui/og/takumi` entry

* add util `replace`, `replaceAll`

* update fs import

* format

* only create `og.ts` for React Router

* update Waku template to use source.ts
This commit is contained in:
Kane Wang
2026-02-22 16:49:24 +08:00
committed by GitHub
parent 1a614de584
commit c684f53349
46 changed files with 775 additions and 167 deletions
@@ -47,13 +47,26 @@ export default defineConfig({
});
```
```ts title="waku.config.ts" tab="Waku"
import { defineConfig } from 'waku/config';
export default defineConfig({
vite: {
ssr: {
// [!code ++]
external: ['@takumi-rs/image-response'],
},
},
});
```
## Metadata Image
You can generate metadata images dynamically using `@takumi-rs/image-response`.
Add the following under your loader, and define image metadata for pages:
```ts title="lib/source.ts"
```ts tab="Next.js" title="lib/source.ts"
import { type InferPageType } from 'fumadocs-core/source';
// [!code ++:8]
@@ -88,15 +101,51 @@ export async function generateMetadata(props: PageProps<'/docs/[[...slug]]'>): P
}
```
```tsx tab="React Router" title="app/lib/og.ts"
export function getPageImage(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return {
segments,
url: `/og/docs/${segments.join('/')}`,
};
}
```
```tsx tab="React Router" title="app/routes/docs.tsx"
// [!code ++]
import { getPageImage } from '@/lib/og';
// Somewhere in your component or client loader
return (
<>
{/* [!code ++] */}
<meta property="og:image" content={getPageImage(page.slugs).url} />
</>
);
```
```tsx tab="Vite Based Framework" title="app/lib/source.ts"
// [!code ++:8]
export function getPageImage(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return {
segments,
url: `/og/docs/${segments.join('/')}`,
};
}
```
```tsx tab="Vite Based Framework" title="app/routes/docs.tsx"
// [!code ++]
import { getPageImage } from '@/lib/source';
// Somewhere in your component or client loader
return (
<>
{/* [!code ++] */}
<meta name="og:image" content={getPageImage(page).url} />
<meta property="og:image" content={getPageImage(page.slugs).url} />
</>
);
```
@@ -109,7 +158,7 @@ Finally, create a route handler to generate images at build time:
import { getPageImage, source } from '@/lib/source';
import { notFound } from 'next/navigation';
import { ImageResponse } from '@takumi-rs/image-response';
import { generate as DefaultImage } from 'fumadocs-ui/og';
import { generate as DefaultImage } from 'fumadocs-ui/og/takumi';
export const revalidate = false;
@@ -136,11 +185,11 @@ export function generateStaticParams() {
}
```
```tsx tab="React Router" title="app/routes/og.tsx"
```tsx tab="React Router" title="app/routes/docs.og.tsx"
import { ImageResponse } from '@takumi-rs/image-response';
import { source } from '@/lib/source';
import { generate as DefaultImage } from 'fumadocs-ui/og';
import type { Route } from './+types/og';
import { generate as DefaultImage } from 'fumadocs-ui/og/takumi';
import type { Route } from './+types/docs.og';
export function loader({ params }: Route.LoaderArgs) {
const slugs = params['*']
@@ -162,47 +211,72 @@ export function loader({ params }: Route.LoaderArgs) {
}
```
```tsx tab="Waku" title="src/pages/_api/og/docs/[...slugs]/image.webp.tsx"
import { source } from '@/lib/source';
import { ImageResponse } from '@takumi-rs/image-response';
import { generate as DefaultImage } from 'fumadocs-ui/og/takumi';
import { ApiContext } from 'waku/router';
export async function GET(_: Request, { params }: ApiContext<'/og/docs/[...slugs]/image.webp'>) {
const page = source.getPage(params.slugs);
if (!page) return new Response(undefined, { status: 404 });
return new ImageResponse(
<DefaultImage title={page.data.title} description={page.data.description} site="My App" />,
{
width: 1200,
height: 630,
format: 'webp',
},
);
}
export async function getConfig() {
const pages = source
.generateParams()
.map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug));
return {
render: 'static' as const,
staticPaths: pages,
} as const;
}
```
```ts tab="React Router" title="app/routes.ts"
import { type RouteConfig, route } from '@react-router/dev/routes';
export default [
// [!code ++]
route('/og/docs/*', 'routes/og.tsx'),
route('/og/docs/*', 'routes/docs.og.tsx'),
] satisfies RouteConfig;
```
```ts tab="React Router" title="react-router.config.ts"
import type { Config } from '@react-router/dev/config';
import { glob } from 'node:fs/promises';
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
// [!code ++:17]
function getDocsPaths() {
const paths = Object.keys(
import.meta.glob('./**/*.{mdx,md}', {
base: './content',
}),
);
return paths.map(relativeFileNameToPath);
}
function relativeFileNameToPath(name: string) {
const path = name.slice(1).replace(/(index)?\.(md|mdx)$/, '');
if (!path.endsWith('/')) return `${path}/`;
return path;
}
const getUrl = createGetUrl('/docs');
export default {
// [!code ++:6]
prerender({ getStaticPaths }) {
const docsPaths = getDocsPaths();
const docsOgPaths = docsPaths.map((path) => `/og${path}image.webp`);
return [...getStaticPaths(), ...docsPaths, ...docsOgPaths];
ssr: true,
future: {
v8_middleware: true,
},
routeDiscovery: {
mode: 'initial',
// [!code ++:12]
async prerender({ getStaticPaths }) {
const paths = [...getStaticPaths()];
for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) {
const slugs = getSlugs(entry);
paths.push(getUrl(slugs));
paths.push(`/og/docs/${[...slugs, 'image.webp'].join('/')}`);
}
return paths;
},
} satisfies Config;
```
+8 -2
View File
@@ -13,7 +13,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"paths": {
"@/*": ["./*"],
@@ -25,6 +25,12 @@
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules"]
}
+5
View File
@@ -0,0 +1,5 @@
export function getPageImagePath(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return `/og/docs/${segments.join('/')}`;
}
+1
View File
@@ -4,6 +4,7 @@ export default [
index('routes/home.tsx'),
route('docs/*', 'routes/docs.tsx'),
route('api/search', 'routes/search.ts'),
route('og/docs/*', 'routes/og.docs.tsx'),
// LLM integration:
route('llms.txt', 'llms/index.ts'),
+7 -2
View File
@@ -7,6 +7,7 @@ import browserCollections from 'fumadocs-mdx:collections/browser';
import { baseOptions, gitConfig } from '@/lib/layout.shared';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { LLMCopyButton, ViewOptions } from '@/components/ai/page-actions';
import { getPageImagePath } from '@/lib/og';
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params['*'].split('/').filter((v) => v.length > 0);
@@ -17,6 +18,7 @@ export async function loader({ params }: Route.LoaderArgs) {
path: page.path,
url: page.url,
pageTree: await source.serializePageTree(source.getPageTree()),
imagePath: getPageImagePath(slugs),
};
}
@@ -27,15 +29,18 @@ const clientLoader = browserCollections.docs.createClientLoader({
{
path,
url,
imagePath,
}: {
path: string;
url: string;
imagePath: string;
},
) {
return (
<DocsPage toc={toc}>
<title>{frontmatter.title}</title>
<meta name="description" content={frontmatter.description} />
<meta property="og:image" content={imagePath} />
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b -mt-4 pb-6">
@@ -54,11 +59,11 @@ const clientLoader = browserCollections.docs.createClientLoader({
});
export default function Page({ loaderData }: Route.ComponentProps) {
const { path, url, pageTree } = useFumadocsLoader(loaderData);
const { path, url, pageTree, imagePath } = useFumadocsLoader(loaderData);
return (
<DocsLayout {...baseOptions()} tree={pageTree}>
{clientLoader.useContent(path, { path, url })}
{clientLoader.useContent(path, { path, url, imagePath })}
</DocsLayout>
);
}
@@ -0,0 +1,23 @@
import { ImageResponse } from '@takumi-rs/image-response';
import type { Route } from './+types/og.docs';
import { generate as DefaultImage } from 'fumadocs-ui/og/takumi';
import { source } from '@/lib/source';
export function loader({ params }: Route.LoaderArgs) {
const slugs = params['*']
.split('/')
.filter((v) => v.length > 0)
.slice(0, -1);
const page = source.getPage(slugs);
if (!page) throw new Response(undefined, { status: 404 });
return new ImageResponse(
<DefaultImage title={page.data.title} description={page.data.description} site="My App" />,
{
width: 1200,
height: 630,
format: 'webp',
},
);
}
+1
View File
@@ -11,6 +11,7 @@
"dependencies": {
"@react-router/node": "^7.13.0",
"@react-router/serve": "^7.13.0",
"@takumi-rs/image-response": "^0.68.17",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
+5 -1
View File
@@ -1,6 +1,7 @@
import type { Config } from '@react-router/dev/config';
import { glob } from 'node:fs/promises';
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
import { getPageImagePath } from './app/lib/og';
const getUrl = createGetUrl('/docs');
@@ -18,7 +19,10 @@ export default {
}
for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) {
paths.push(getUrl(getSlugs(entry)));
const slugs = getSlugs(entry);
paths.push(getUrl(slugs));
paths.push(getPageImagePath(slugs));
}
return paths;
+3
View File
@@ -14,4 +14,7 @@ export default defineConfig({
projects: ['./tsconfig.json'],
}),
],
ssr: {
external: ['@takumi-rs/image-response'],
},
});
+1
View File
@@ -10,6 +10,7 @@
"types:check": "fumadocs-mdx && tsc --noEmit"
},
"dependencies": {
"@takumi-rs/image-response": "^0.68.17",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
+9
View File
@@ -7,3 +7,12 @@ export const source = loader({
baseUrl: '/docs',
plugins: [lucideIconsPlugin()],
});
export function getPageImage(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return {
segments,
url: `/og/docs/${segments.join('/')}`,
};
}
@@ -0,0 +1,30 @@
import { source } from '@/lib/source';
import { ImageResponse } from '@takumi-rs/image-response';
import { generate as DefaultImage } from 'fumadocs-ui/og/takumi';
import { ApiContext } from 'waku/router';
export async function GET(_: Request, { params }: ApiContext<'/og/docs/[...slugs]/image.webp'>) {
const page = source.getPage(params.slugs);
if (!page) return new Response(undefined, { status: 404 });
return new ImageResponse(
<DefaultImage title={page.data.title} description={page.data.description} site="My App" />,
{
width: 1200,
height: 630,
format: 'webp',
},
);
}
export async function getConfig() {
const pages = source
.generateParams()
.map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug));
return {
render: 'static' as const,
staticPaths: pages,
} as const;
}
@@ -2,6 +2,7 @@ import { source } from '@/lib/source';
import { PageProps } from 'waku/router';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { getPageImage } from '@/lib/source';
export default function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) {
const page = source.getPage(slugs);
@@ -20,6 +21,7 @@ export default function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) {
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc}>
<meta property="og:image" content={getPageImage(slugs).url} />
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
+3
View File
@@ -11,6 +11,9 @@ export default defineConfig({
optimizeDeps: {
exclude: ['fumadocs-ui', 'fumadocs-core'],
},
ssr: {
external: ['@takumi-rs/image-response'],
},
plugins: [tailwindcss(), mdx(MdxConfig), tsconfigPaths()],
} satisfies UserConfig as Config['vite'],
+8
View File
@@ -85,6 +85,10 @@
"types": "./dist/og.d.ts",
"import": "./dist/og.js"
},
"./og/*": {
"types": "./dist/og/*.d.ts",
"import": "./dist/og/*.js"
},
"./utils/*": {
"types": "./dist/utils/*.d.ts",
"import": "./dist/utils/*.js"
@@ -131,6 +135,7 @@
"unified": "^11.0.5"
},
"peerDependencies": {
"@takumi-rs/image-response": "^0.68.17",
"@types/react": "*",
"fumadocs-core": "workspace:*",
"next": "16.x.x",
@@ -142,6 +147,9 @@
"next": {
"optional": true
},
"@takumi-rs/image-response": {
"optional": true
},
"@types/react": {
"optional": true
},
+3
View File
@@ -64,6 +64,7 @@ export function generate({
style={{
fontSize: '56px',
fontWeight: 600,
margin: 0,
}}
>
{props.site}
@@ -74,6 +75,7 @@ export function generate({
style={{
fontWeight: 800,
fontSize: '82px',
margin: 0,
}}
>
{props.title}
@@ -82,6 +84,7 @@ export function generate({
style={{
fontSize: '52px',
color: 'rgba(240,240,240,0.8)',
margin: 0,
}}
>
{props.description}
+93
View File
@@ -0,0 +1,93 @@
import { ImageResponse, type ImageResponseOptions } from '@takumi-rs/image-response';
import type { ReactNode } from 'react';
interface GenerateProps {
title: ReactNode;
description?: ReactNode;
icon?: ReactNode;
primaryColor?: string;
primaryTextColor?: string;
site?: ReactNode;
}
export function generateOGImage(options: GenerateProps & ImageResponseOptions): ImageResponse {
const { title, description, icon, site, primaryColor, primaryTextColor, ...rest } = options;
return new ImageResponse(
generate({
title,
description,
icon,
site,
primaryTextColor,
primaryColor,
}),
{
width: 1200,
height: 630,
...rest,
},
);
}
export function generate({
primaryColor = 'rgba(255,150,255,0.3)',
primaryTextColor = 'rgb(255,150,255)',
...props
}: GenerateProps) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
color: 'white',
padding: '4rem',
backgroundColor: '#0c0c0c',
backgroundImage: `linear-gradient(to top right, ${primaryColor}, transparent)`,
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
marginBottom: '12px',
color: primaryTextColor,
}}
>
{props.icon}
<p
style={{
fontSize: '56px',
fontWeight: 600,
margin: 0,
}}
>
{props.site}
</p>
</div>
<p
style={{
fontWeight: 800,
fontSize: '82px',
margin: 0,
}}
>
{props.title}
</p>
<p
style={{
fontSize: '52px',
color: 'rgba(240,240,240,0.8)',
margin: 0,
}}
>
{props.description}
</p>
</div>
);
}
+1 -1
View File
@@ -7,7 +7,7 @@ export default defineConfig({
target: 'es2023',
entry: [
'./src/*.{ts,tsx}',
'./src/{components,tailwind,contexts,layouts,provider}/**/*.{ts,tsx}',
'./src/{components,tailwind,contexts,layouts,provider,og}/**/*.{ts,tsx}',
'./src/utils/use-*.{ts,tsx}',
],
fixedExtension: false,
@@ -6,6 +6,7 @@
"dependencies": {
"@biomejs/biome": "^2.4.2",
"@orama/core": "^1.2.18",
"@takumi-rs/image-response": "^0.68.17",
"eslint": "^10.0.0",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
+30
View File
@@ -32,6 +32,9 @@ const command = program
.addOption(
new Option('--search <name>', 'configure a search solution').choices(['orama', 'orama-cloud']),
)
.addOption(
new Option('--og-image <name>', 'configure OG image generation').choices(['next-og', 'takumi']),
)
.addOption(
new Option('--template <name>', 'choose a template').choices(
templates.map((item) => item.value),
@@ -133,6 +136,28 @@ async function main(): Promise<void> {
],
});
},
ogImage: async ({ results }: { results: { template?: Template } }) => {
if (config.ogImage !== undefined) return config.ogImage;
if (isCI) return 'takumi';
return select({
message: 'Configure Open Graph Image generation?',
options: [
{
value: 'takumi',
label: 'Takumi',
hint: 'Output WebP format, framework-agnostic',
},
{
value: 'next-og',
label: 'next/og',
hint: 'Next.js built-in solution',
disabled: !results.template?.startsWith('+next'),
},
],
initialValue: 'takumi' as const,
});
},
installDeps: async () => {
if (config.install !== undefined) return config.install;
if (isCI) return false;
@@ -177,6 +202,11 @@ async function main(): Promise<void> {
plugins.push(biome());
}
if (options.ogImage) {
const { ogImage } = await import('./plugins/og-image');
plugins.push(ogImage(options.ogImage));
}
await create({
packageManager: config.pm,
template: options.template,
@@ -0,0 +1,64 @@
import { TemplatePlugin, TemplatePluginContext } from '@/index';
import { depVersions } from '@/constants';
import { pick, replace, replaceAll } from '@/utils';
import { join } from 'node:path';
import { readFile, writeFile } from 'node:fs/promises';
export function ogImage(type: 'next-og' | 'takumi'): TemplatePlugin {
return {
packageJson(packageJson) {
if (this.template.value.startsWith('+next') && type === 'takumi') {
return {
...packageJson,
dependencies: {
...packageJson.dependencies,
...pick(depVersions, ['@takumi-rs/image-response']),
},
};
}
return packageJson;
},
async afterWrite() {
if (this.template.value.startsWith('+next') && type === 'takumi') {
await replaceImports(this);
await replaceImagePath(this);
await nextConfigExternal(this);
}
},
};
}
async function replaceImports(context: TemplatePluginContext) {
const path = join(context.appDir, 'app/og/docs/[...slug]/route.tsx');
const content = await readFile(path, 'utf-8');
let replaced = replaceAll(content, 'next/og', '@takumi-rs/image-response');
replaced = replaceAll(replaced, 'fumadocs-ui/og', 'fumadocs-ui/og/takumi');
replaced = replace(replaced, 'height: 630,', "height: 630,\n format: 'webp',");
await writeFile(path, replaced);
}
async function replaceImagePath(context: TemplatePluginContext) {
const path = join(context.appDir, 'lib/source.ts');
const content = await readFile(path, 'utf-8');
const replaced = replaceAll(content, 'image.png', 'image.webp');
await writeFile(path, replaced);
}
async function nextConfigExternal(context: TemplatePluginContext) {
const path = join(context.appDir, 'next.config.mjs');
const content = await readFile(path, 'utf-8');
const replaced = replace(
content,
'const config = {',
`const config = {
serverExternalPackages: ['@takumi-rs/image-response'],
`,
);
await writeFile(path, replaced);
}
+16
View File
@@ -108,3 +108,19 @@ export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pi
return result as Pick<T, K>;
}
export function replace(content: string, target: string | RegExp, replacement: string): string {
const result = content.replace(target, replacement);
if (result === content) {
throw new Error(`Failed to replace "${target.toString()}"`);
}
return result;
}
export function replaceAll(content: string, target: string | RegExp, replacement: string): string {
const result = content.replaceAll(target, replacement);
if (result === content) {
throw new Error(`Failed to replace all "${target.toString()}"`);
}
return result;
}
@@ -12,5 +12,6 @@ export function baseOptions(): BaseLayoutProps {
nav: {
title: 'My App',
},
githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`,
};
}
@@ -13,11 +13,11 @@
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"lucide-react": "^0.563.0",
"lucide-react": "^0.570.0",
"next": "16.1.6",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.4.0"
"tailwind-merge": "^3.4.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.18",
@@ -12,11 +12,11 @@
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"lucide-react": "^0.563.0",
"lucide-react": "^0.570.0",
"next": "16.1.6",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.4.0"
"tailwind-merge": "^3.4.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.18",
@@ -15,11 +15,11 @@
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"isbot": "^5.1.35",
"lucide-react": "^0.563.0",
"lucide-react": "^0.570.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "^7.13.0",
"tailwind-merge": "^3.4.0"
"tailwind-merge": "^3.4.1"
},
"devDependencies": {
"@react-router/dev": "^7.13.0",
@@ -0,0 +1,5 @@
export function getPageImagePath(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return `/og/docs/${segments.join('/')}`;
}
@@ -4,6 +4,7 @@ export default [
index('routes/home.tsx'),
route('docs/*', 'routes/docs.tsx'),
route('api/search', 'routes/search.ts'),
route('og/docs/*', 'routes/og.docs.tsx'),
// LLM integration:
route('llms.txt', 'llms/index.ts'),
@@ -7,6 +7,7 @@ import browserCollections from 'fumadocs-mdx:collections/browser';
import { baseOptions, gitConfig } from '@/lib/layout.shared';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { LLMCopyButton, ViewOptions } from '@/components/ai/page-actions';
import { getPageImagePath } from '@/lib/og';
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params['*'].split('/').filter((v) => v.length > 0);
@@ -17,6 +18,7 @@ export async function loader({ params }: Route.LoaderArgs) {
path: page.path,
url: page.url,
pageTree: await source.serializePageTree(source.getPageTree()),
imagePath: getPageImagePath(slugs),
};
}
@@ -27,15 +29,18 @@ const clientLoader = browserCollections.docs.createClientLoader({
{
path,
url,
imagePath,
}: {
path: string;
url: string;
imagePath: string;
},
) {
return (
<DocsPage toc={toc}>
<title>{frontmatter.title}</title>
<meta name="description" content={frontmatter.description} />
<meta property="og:image" content={imagePath} />
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b -mt-4 pb-6">
@@ -54,11 +59,11 @@ const clientLoader = browserCollections.docs.createClientLoader({
});
export default function Page({ loaderData }: Route.ComponentProps) {
const { path, url, pageTree } = useFumadocsLoader(loaderData);
const { path, url, pageTree, imagePath } = useFumadocsLoader(loaderData);
return (
<DocsLayout {...baseOptions()} tree={pageTree}>
{clientLoader.useContent(path, { path, url })}
{clientLoader.useContent(path, { path, url, imagePath })}
</DocsLayout>
);
}
@@ -0,0 +1,23 @@
import { ImageResponse } from '@takumi-rs/image-response';
import type { Route } from './+types/og.docs';
import { generate as DefaultImage } from 'fumadocs-ui/og/takumi';
import { source } from '@/lib/source';
export function loader({ params }: Route.LoaderArgs) {
const slugs = params['*']
.split('/')
.filter((v) => v.length > 0)
.slice(0, -1);
const page = source.getPage(slugs);
if (!page) throw new Response(undefined, { status: 404 });
return new ImageResponse(
<DefaultImage title={page.data.title} description={page.data.description} site="My App" />,
{
width: 1200,
height: 630,
format: 'webp',
},
);
}
@@ -11,15 +11,16 @@
"dependencies": {
"@react-router/node": "^7.13.0",
"@react-router/serve": "^7.13.0",
"@takumi-rs/image-response": "^0.68.17",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"isbot": "^5.1.35",
"lucide-react": "^0.563.0",
"lucide-react": "^0.570.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "^7.13.0",
"tailwind-merge": "^3.4.0"
"tailwind-merge": "^3.4.1"
},
"devDependencies": {
"@react-router/dev": "^7.13.0",
@@ -1,6 +1,7 @@
import type { Config } from '@react-router/dev/config';
import { glob } from 'node:fs/promises';
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
import { getPageImagePath } from './app/lib/og';
const getUrl = createGetUrl('/docs');
@@ -18,7 +19,10 @@ export default {
}
for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) {
paths.push(getUrl(getSlugs(entry)));
const slugs = getSlugs(entry);
paths.push(getUrl(slugs));
paths.push(getPageImagePath(slugs));
}
return paths;
@@ -1,5 +1,5 @@
{
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
"include": ["**/*", ".react-router/types/**/*"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
@@ -14,4 +14,7 @@ export default defineConfig({
projects: ['./tsconfig.json'],
}),
],
ssr: {
external: ['@takumi-rs/image-response'],
},
});
@@ -12,17 +12,17 @@
},
"dependencies": {
"@orama/orama": "^3.1.18",
"@tanstack/react-router": "1.159.5",
"@tanstack/react-router-devtools": "1.159.5",
"@tanstack/react-start": "1.159.5",
"@tanstack/start-static-server-functions": "1.159.5",
"@tanstack/react-router": "1.160.2",
"@tanstack/react-router-devtools": "1.160.2",
"@tanstack/react-start": "1.160.2",
"@tanstack/start-static-server-functions": "1.160.2",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"lucide-react": "^0.563.0",
"lucide-react": "^0.570.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.4.0",
"tailwind-merge": "^3.4.1",
"vite": "^7.3.1"
},
"devDependencies": {
@@ -34,7 +34,7 @@
"@vitejs/plugin-react": "^5.1.4",
"nitro": "npm:[email protected]",
"serve": "^14.2.5",
"srvx": "^0.11.4",
"srvx": "^0.11.5",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^6.1.1"
@@ -11,16 +11,16 @@
"types:check": "fumadocs-mdx && tsc --noEmit"
},
"dependencies": {
"@tanstack/react-router": "1.159.5",
"@tanstack/react-router-devtools": "1.159.5",
"@tanstack/react-start": "1.159.5",
"@tanstack/react-router": "1.160.2",
"@tanstack/react-router-devtools": "1.160.2",
"@tanstack/react-start": "1.160.2",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"lucide-react": "^0.563.0",
"lucide-react": "^0.570.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwind-merge": "^3.4.0",
"tailwind-merge": "^3.4.1",
"vite": "^7.3.1"
},
"devDependencies": {
@@ -31,7 +31,7 @@
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",
"nitro": "npm:[email protected]",
"srvx": "^0.11.4",
"srvx": "^0.11.5",
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^6.1.1"
@@ -10,14 +10,15 @@
"types:check": "fumadocs-mdx && tsc --noEmit"
},
"dependencies": {
"@takumi-rs/image-response": "^0.68.17",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"lucide-react": "^0.563.0",
"lucide-react": "^0.570.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-server-dom-webpack": "^19.2.4",
"waku": "1.0.0-alpha.3"
"waku": "1.0.0-alpha.4"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.18",
@@ -7,3 +7,12 @@ export const source = loader({
baseUrl: '/docs',
plugins: [lucideIconsPlugin()],
});
export function getPageImage(slugs: string[]) {
const segments = [...slugs, 'image.webp'];
return {
segments,
url: `/og/docs/${segments.join('/')}`,
};
}
@@ -0,0 +1,30 @@
import { source } from '@/lib/source';
import { ImageResponse } from '@takumi-rs/image-response';
import { generate as DefaultImage } from 'fumadocs-ui/og/takumi';
import { ApiContext } from 'waku/router';
export async function GET(_: Request, { params }: ApiContext<'/og/docs/[...slugs]/image.webp'>) {
const page = source.getPage(params.slugs);
if (!page) return new Response(undefined, { status: 404 });
return new ImageResponse(
<DefaultImage title={page.data.title} description={page.data.description} site="My App" />,
{
width: 1200,
height: 630,
format: 'webp',
},
);
}
export async function getConfig() {
const pages = source
.generateParams()
.map((item) => (item.lang ? [item.lang, ...item.slug] : item.slug));
return {
render: 'static' as const,
staticPaths: pages,
} as const;
}
@@ -2,6 +2,7 @@ import { source } from '@/lib/source';
import { PageProps } from 'waku/router';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { getPageImage } from '@/lib/source';
export default function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) {
const page = source.getPage(slugs);
@@ -20,6 +21,7 @@ export default function DocPage({ slugs }: PageProps<'/docs/[...slugs]'>) {
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc}>
<meta property="og:image" content={getPageImage(slugs).url} />
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
@@ -11,6 +11,9 @@ export default defineConfig({
optimizeDeps: {
exclude: ['fumadocs-ui', 'fumadocs-core'],
},
ssr: {
external: ['@takumi-rs/image-response'],
},
plugins: [tailwindcss(), mdx(MdxConfig), tsconfigPaths()],
} satisfies UserConfig as Config['vite'],
+8
View File
@@ -89,6 +89,10 @@
"types": "./dist/og.d.ts",
"import": "./dist/og.js"
},
"./og/*": {
"types": "./dist/og/*.d.ts",
"import": "./dist/og/*.js"
},
"./utils/get-sidebar-tabs": {
"types": "./dist/components/sidebar/tabs/index.d.ts",
"import": "./dist/components/sidebar/tabs/index.js"
@@ -148,6 +152,7 @@
"unified": "^11.0.5"
},
"peerDependencies": {
"@takumi-rs/image-response": "^0.68.17",
"@types/react": "*",
"fumadocs-core": "workspace:*",
"next": "16.x.x",
@@ -158,6 +163,9 @@
"next": {
"optional": true
},
"@takumi-rs/image-response": {
"optional": true
},
"@types/react": {
"optional": true
}
+3
View File
@@ -64,6 +64,7 @@ export function generate({
style={{
fontSize: '56px',
fontWeight: 600,
margin: 0,
}}
>
{props.site}
@@ -74,6 +75,7 @@ export function generate({
style={{
fontWeight: 800,
fontSize: '82px',
margin: 0,
}}
>
{props.title}
@@ -82,6 +84,7 @@ export function generate({
style={{
fontSize: '52px',
color: 'rgba(240,240,240,0.8)',
margin: 0,
}}
>
{props.description}
+93
View File
@@ -0,0 +1,93 @@
import { ImageResponse, type ImageResponseOptions } from '@takumi-rs/image-response';
import type { ReactNode } from 'react';
interface GenerateProps {
title: ReactNode;
description?: ReactNode;
icon?: ReactNode;
primaryColor?: string;
primaryTextColor?: string;
site?: ReactNode;
}
export function generateOGImage(options: GenerateProps & ImageResponseOptions): ImageResponse {
const { title, description, icon, site, primaryColor, primaryTextColor, ...rest } = options;
return new ImageResponse(
generate({
title,
description,
icon,
site,
primaryTextColor,
primaryColor,
}),
{
width: 1200,
height: 630,
...rest,
},
);
}
export function generate({
primaryColor = 'rgba(255,150,255,0.3)',
primaryTextColor = 'rgb(255,150,255)',
...props
}: GenerateProps) {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
color: 'white',
padding: '4rem',
backgroundColor: '#0c0c0c',
backgroundImage: `linear-gradient(to top right, ${primaryColor}, transparent)`,
}}
>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '16px',
marginBottom: '12px',
color: primaryTextColor,
}}
>
{props.icon}
<p
style={{
fontSize: '56px',
fontWeight: 600,
margin: 0,
}}
>
{props.site}
</p>
</div>
<p
style={{
fontWeight: 800,
fontSize: '82px',
margin: 0,
}}
>
{props.title}
</p>
<p
style={{
fontSize: '52px',
color: 'rgba(240,240,240,0.8)',
margin: 0,
}}
>
{props.description}
</p>
</div>
);
}
+1 -1
View File
@@ -7,7 +7,7 @@ export default defineConfig({
target: 'es2023',
entry: [
'./src/*.{ts,tsx}',
'./src/{components,contexts,layouts,provider,tailwind}/**/*.{ts,tsx}',
'./src/{components,contexts,layouts,provider,tailwind,og}/**/*.{ts,tsx}',
'./src/utils/use-*.{ts,tsx}',
],
fixedExtension: false,
+134 -100
View File
@@ -330,7 +330,7 @@ importers:
devDependencies:
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18(vite@6.4.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
version: 4.1.18(vite@7.3.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
'@types/react':
specifier: ^19.2.14
version: 19.2.14
@@ -888,6 +888,9 @@ importers:
'@react-router/serve':
specifier: ^7.13.0
version: 7.13.0([email protected]([email protected]([email protected]))([email protected]))([email protected])
'@takumi-rs/image-response':
specifier: ^0.68.17
version: 0.68.17
fumadocs-core:
specifier: workspace:*
version: link:../../packages/core
@@ -918,7 +921,7 @@ importers:
devDependencies:
'@react-router/dev':
specifier: ^7.13.0
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
@@ -985,7 +988,7 @@ importers:
devDependencies:
'@react-router/dev':
specifier: ^7.13.0
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
@@ -1049,7 +1052,7 @@ importers:
devDependencies:
'@react-router/dev':
specifier: ^7.13.0
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
@@ -1113,7 +1116,7 @@ importers:
devDependencies:
'@react-router/dev':
specifier: ^7.13.0
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
@@ -1186,7 +1189,7 @@ importers:
devDependencies:
'@react-router/dev':
specifier: ^7.13.0
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
version: 7.13.0(@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
'@tailwindcss/vite':
specifier: ^4.1.18
version: 4.1.18([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
@@ -1304,7 +1307,7 @@ importers:
version: 1.160.2(@tanstack/[email protected]([email protected]([email protected]))([email protected]))(@tanstack/[email protected])([email protected])([email protected]([email protected]))([email protected])
'@tanstack/react-start':
specifier: 1.160.2
version: 1.160.2([email protected])([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
version: 1.160.2([email protected]([email protected]))([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
fumadocs-core:
specifier: workspace:*
version: link:../../packages/core
@@ -1517,7 +1520,7 @@ importers:
version: 1.160.2(@tanstack/[email protected]([email protected]([email protected]))([email protected]))(@tanstack/[email protected])([email protected])([email protected]([email protected]))([email protected])
'@tanstack/react-start':
specifier: 1.160.2
version: 1.160.2([email protected])([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
version: 1.160.2([email protected]([email protected]))([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
fumadocs-core:
specifier: workspace:*
version: link:../../packages/core
@@ -1576,6 +1579,9 @@ importers:
examples/waku:
dependencies:
'@takumi-rs/image-response':
specifier: ^0.68.17
version: 0.68.17
fumadocs-core:
specifier: workspace:*
version: link:../../packages/core
@@ -1692,6 +1698,9 @@ importers:
'@fumadocs/tailwind':
specifier: workspace:*
version: link:../tailwind
'@takumi-rs/image-response':
specifier: ^0.68.17
version: 0.68.17
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -2054,6 +2063,9 @@ importers:
'@orama/core':
specifier: ^1.2.18
version: 1.2.18
'@takumi-rs/image-response':
specifier: ^0.68.17
version: 0.68.17
eslint:
specifier: ^10.0.0
version: 10.0.0([email protected])
@@ -2645,6 +2657,9 @@ importers:
'@radix-ui/react-tabs':
specifier: ^1.1.13
version: 1.1.13(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected]([email protected]))([email protected])
'@takumi-rs/image-response':
specifier: ^0.68.17
version: 0.68.17
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -6426,64 +6441,125 @@ packages:
peerDependencies:
vite: ^5.2.0 || ^6 || ^7
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-toMlVnB19J+eUVDOV3mhVOqFn+fMazycMZoeQSJQR/GXtog2peRp2dKqSE8w6gi7nGUOrZzX4u4L+eX+vwnfww==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [darwin]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-JCk4F53rB9h9lFkLj1S55CwYKO4fB2OqQhnakZMgEPs1HvdpH0xYLjzxGWrG075jhFL1alvZrAQxQYG5e8RJUw==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [darwin]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-z4rQ4QLCIeHm9AWLiPBU8gG4gPTScFUC8zKFgCxlYMORgNcnija7VoeBfhc04rLL3h7LWWHPrp7Q2V8r7cpMHw==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [darwin]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-/cvi1wtDcLJjnRQk2wtS1iDq2CRZQzIr7ndPVqFjRYbCh2xWCl66s8KBCIiXgkaqWpC85adW0e2pBilIoj5GyQ==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [darwin]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-PRyL7KEujZDskgVf16KVkjVRgTw2QhznRmYYa/1YI2PWXcYvheEtOMRCL2B7dLbmFVsh1agXYMQrBEBEyH0YUA==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-birLK4nOFgd0qfVWsHIsfl7R14vMaqFOevklF437JH526BUuDLnWvOFc6iyckb5vtIGDQy9Z3NrP/KgeoIqyRw==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-4CiEF518wDnujF0fjql2XN6uO+OXl0svy0WgAF2656dCx2gJtWscaHytT2rsQ0ZmoFWE0dyWcDW1g/FBVPvuvA==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-Y1DImklVf06pLUYk0TUNLy/DwYtis0ewqECe0sdY7yHWA2n+4lAIff74uJT2tmLqOG0AmxONSFtvy1lOGfNdiQ==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-jm8lTe2E6Tfq2b97GJC31TWK1JAEv+MsVbvL9DCLlYcafgYFlMXDUnOkZFMjlrmh0HcFAYDaBkniNDgIQfXqzg==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-uxFeTPcw/nYvAkDFp2F2LAMS1bmCOrRwJsF5IiK8mJAmrr4iqRfKAKk7S1B1Lbsw4n1SQslbbb0yD853F/UTKA==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-nbdzQgC4ywzltDDV1fer1cKswwGE+xXZHdDiacdd7RM5XBng209Bmo3j1iv9dsX+4xXhByzCCGbxdWhhHqVXmw==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-cBtwuxUMg7M7w5ih8FURUmIViqTt7Id5ityLd1qgR3EDbN6TtClJnG0jDUnuPX2JRbZd/BpZWZxDJbT65G+HGQ==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [linux]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-kE4F0LRmuhSwiNkFG7dTY9ID8+B7zb97QedyN/IO2fBJmRQDkqCGcip2gloh8YPPhCuKGjCqqqh2L+Tg9PKW7w==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [win32]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-Zuvkvgv2o+UMaSUpJKXY+9HLJrSc+9yhvVtG9h8+9CKyz7k9kOJKWrGsumU2xCVmHHVk58o0dOushIRwCc8VIQ==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [arm64]
os: [win32]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-oPbpEsc/POZcTzE1Om+nEI3IQs687iD/W2DMzs1LRlG4VY1vDF2m2mI7a0R3nQgFdOYKX3kkFIEsxY5qSTstnw==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [win32]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-r7o+R/UT2QSdxOzXjNk1+phxgqlP3cfOSvCzRjko6vbWnoXLqX38uvzOFWnUpgiBOwKUO9xAGmx6KfjpFxYUtA==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
cpu: [x64]
os: [win32]
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-kW4Z/93RkBZHVF5aoc4mh9kbyEJam0kGv/RbZKdUpg7ZQ97uetgJ/pY3OwcSsRY9bL5WmlBm2+s+EW814WXqpQ==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-3Uj7kzdyVpRq66uW2MGVD2bcURaQN86HARNNAhHBDbV45jfnx0BwCQfrTHPvSQzGxZ0sbrynwwTVretXyAATOw==}
engines: {node: '>= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0'}
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-aREzIBHpGaUKqvl371UFx+HspMtcN9Y6SgwQ2HQZ8VcLn/C4uJYHkk4vg2Z0cIJsiMxxvc+8d58J/9sxzwc0vA==}
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-vwhfTk+DXJ9WFBmAhWR38mFlYzWgy60oqcF84SNxtBd3vFF8misJIGSEEYlIZjj+1EKYCmszKbrH+CLvxWySUw==}
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-Crnmfe7+ml3zACcS+mpgi7yJ0oTtJgXRsdphsDv0wY5LWNdKrNNMCsUE5Qp1zs7AdcuzWL0bYyBctRlImRTTdQ==}
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-LiQ243RC4wNQKLKC8HH6wRyBsTSKLyBeLGwfm0UzGOMUWMeAtVbCzcy5G6nPifdPcr01btmV2LouEP6CCBBLog==}
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-5fUGeQg+T8+m4huLxN6CjGvhCSD400WfO8J0X9Ud8ULpefFj3rP3a+eh9AhezlF9HqeltS+ODzB2BFbmmHCTVA==}
'@takumi-rs/[email protected]':
resolution: {integrity: sha512-eklfvfrWjwhR1XQHMI0Tt+1WZINsvMeKBrT3pq7G5IyM8P9e61mH28NRs0cyFCDxHu+lgGqHzyJylFBp7iKgtQ==}
@@ -7512,6 +7588,7 @@ packages:
[email protected]:
resolution: {integrity: sha512-v5hkh1us7sMNjfimWE70flYbD5I1/qWQaqmJ45q2qk5H/7muQVa478LSVRSFyGTBUBog2LsPQnfIRdjyWJRY+A==}
cpu: [arm64, x64]
os: [darwin, linux, win32]
hasBin: true
@@ -15016,7 +15093,7 @@ snapshots:
'@radix-ui/[email protected]': {}
'@react-router/[email protected](@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])':
'@react-router/[email protected](@react-router/[email protected]([email protected]([email protected]([email protected]))([email protected]))([email protected]))(@types/[email protected])(@vitejs/[email protected]([email protected]([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])))([email protected])([email protected])([email protected])([email protected]([email protected]([email protected]))([email protected]))([email protected]([email protected]([email protected]))([email protected])([email protected]))([email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])':
dependencies:
'@babel/core': 7.29.0
'@babel/generator': 7.29.1
@@ -15785,13 +15862,6 @@ snapshots:
postcss: 8.5.6
tailwindcss: 4.1.18
'@tailwindcss/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))':
dependencies:
'@tailwindcss/node': 4.1.18
'@tailwindcss/oxide': 4.1.18
tailwindcss: 4.1.18
vite: 6.4.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
'@tailwindcss/[email protected]([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))':
dependencies:
'@tailwindcss/node': 4.1.18
@@ -15799,30 +15869,65 @@ snapshots:
tailwindcss: 4.1.18
vite: 7.3.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optional: true
'@takumi-rs/[email protected]':
optionalDependencies:
'@takumi-rs/core-darwin-arm64': 0.68.17
'@takumi-rs/core-darwin-x64': 0.68.17
'@takumi-rs/core-linux-arm64-gnu': 0.68.17
'@takumi-rs/core-linux-arm64-musl': 0.68.17
'@takumi-rs/core-linux-x64-gnu': 0.68.17
'@takumi-rs/core-linux-x64-musl': 0.68.17
'@takumi-rs/core-win32-arm64-msvc': 0.68.17
'@takumi-rs/core-win32-x64-msvc': 0.68.17
'@takumi-rs/[email protected]':
optionalDependencies:
'@takumi-rs/core-darwin-arm64': 0.68.4
@@ -15834,14 +15939,24 @@ snapshots:
'@takumi-rs/core-win32-arm64-msvc': 0.68.4
'@takumi-rs/core-win32-x64-msvc': 0.68.4
'@takumi-rs/[email protected]': {}
'@takumi-rs/[email protected]': {}
'@takumi-rs/[email protected]':
dependencies:
'@takumi-rs/core': 0.68.17
'@takumi-rs/helpers': 0.68.17
'@takumi-rs/wasm': 0.68.17
'@takumi-rs/[email protected]':
dependencies:
'@takumi-rs/core': 0.68.4
'@takumi-rs/helpers': 0.68.4
'@takumi-rs/wasm': 0.68.4
'@takumi-rs/[email protected]': {}
'@takumi-rs/[email protected]': {}
'@tanstack/[email protected]':
@@ -15958,18 +16073,6 @@ snapshots:
transitivePeerDependencies:
- crossws
'@tanstack/[email protected]([email protected])([email protected]([email protected]))([email protected])':
dependencies:
'@tanstack/history': 1.154.14
'@tanstack/react-router': 1.160.2([email protected]([email protected]))([email protected])
'@tanstack/router-core': 1.160.0
'@tanstack/start-client-core': 1.160.0
'@tanstack/start-server-core': 1.160.0([email protected])
react: 19.2.4
react-dom: 19.2.4([email protected])
transitivePeerDependencies:
- crossws
'@tanstack/[email protected]([email protected]([email protected]))([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])':
dependencies:
'@tanstack/react-router': 1.160.2([email protected]([email protected]))([email protected])
@@ -15990,26 +16093,6 @@ snapshots:
- vite-plugin-solid
- webpack
'@tanstack/[email protected]([email protected])([email protected]([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])':
dependencies:
'@tanstack/react-router': 1.160.2([email protected]([email protected]))([email protected])
'@tanstack/react-start-client': 1.160.2([email protected]([email protected]))([email protected])
'@tanstack/react-start-server': 1.160.2([email protected])([email protected]([email protected]))([email protected])
'@tanstack/router-utils': 1.158.0
'@tanstack/start-client-core': 1.160.0
'@tanstack/start-plugin-core': 1.160.2(@tanstack/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
'@tanstack/start-server-core': 1.160.0([email protected])
pathe: 2.0.3
react: 19.2.4
react-dom: 19.2.4([email protected])
vite: 7.3.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
transitivePeerDependencies:
- '@rsbuild/core'
- crossws
- supports-color
- vite-plugin-solid
- webpack
'@tanstack/[email protected]([email protected]([email protected]))([email protected])':
dependencies:
'@tanstack/store': 0.8.1
@@ -16126,55 +16209,13 @@ snapshots:
- vite-plugin-solid
- webpack
'@tanstack/[email protected](@tanstack/[email protected]([email protected]([email protected]))([email protected]))([email protected])([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])':
dependencies:
'@babel/code-frame': 7.27.1
'@babel/core': 7.29.0
'@babel/types': 7.29.0
'@rolldown/pluginutils': 1.0.0-beta.40
'@tanstack/router-core': 1.160.0
'@tanstack/router-generator': 1.160.1
'@tanstack/router-plugin': 1.160.2(@tanstack/[email protected]([email protected]([email protected]))([email protected]))([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))([email protected])
'@tanstack/router-utils': 1.158.0
'@tanstack/start-client-core': 1.160.0
'@tanstack/start-server-core': 1.160.0([email protected])
cheerio: 1.2.0
exsolve: 1.0.8
pathe: 2.0.3
srvx: 0.11.5
tinyglobby: 0.2.15
ufo: 1.6.3
vite: 7.3.1(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
vitefu: 1.1.1([email protected](@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected]))
xmlbuilder2: 4.0.3
zod: 3.25.76
transitivePeerDependencies:
- '@rsbuild/core'
- '@tanstack/react-router'
- crossws
- supports-color
- vite-plugin-solid
- webpack
'@tanstack/[email protected]([email protected]([email protected]))':
dependencies:
'@tanstack/history': 1.154.14
'@tanstack/router-core': 1.160.0
'@tanstack/start-client-core': 1.160.0
'@tanstack/start-storage-context': 1.160.0
h3-v2: [email protected]([email protected]([email protected]0.1))
seroval: 1.5.0
tiny-invariant: 1.3.3
transitivePeerDependencies:
- crossws
'@tanstack/[email protected]([email protected])':
dependencies:
'@tanstack/history': 1.154.14
'@tanstack/router-core': 1.160.0
'@tanstack/start-client-core': 1.160.0
'@tanstack/start-storage-context': 1.160.0
h3-v2: [email protected]([email protected])
h3-v2: [email protected]([email protected]([email protected]1.5))
seroval: 1.5.0
tiny-invariant: 1.3.3
transitivePeerDependencies:
@@ -18862,14 +18903,7 @@ snapshots:
ufo: 1.6.3
uncrypto: 0.1.3
[email protected]([email protected]([email protected]0.1)):
dependencies:
rou3: 0.7.12
srvx: 0.11.5
optionalDependencies:
crossws: 0.4.4([email protected])
[email protected]([email protected]):
[email protected]([email protected]([email protected]1.5)):
dependencies:
rou3: 0.7.12
srvx: 0.11.5
@@ -20457,7 +20491,7 @@ snapshots:
consola: 3.4.2
crossws: 0.4.4([email protected])
db0: 0.3.4
h3: 2.0.1-rc.14([email protected]([email protected]0.1))
h3: 2.0.1-rc.14([email protected]([email protected]1.5))
jiti: 2.6.1
nf3: 0.3.7
ofetch: 2.0.0-alpha.3