Merge pull request #14 from Dokploy/feat/new-examples
feat: add initial HTML example and README documentation
@@ -20,6 +20,8 @@ This repository contains examples of how to deploy applications using Dokploy.
|
|||||||
- [x] Svelte
|
- [x] Svelte
|
||||||
- [x] T3 App
|
- [x] T3 App
|
||||||
- [x] Turborepo
|
- [x] Turborepo
|
||||||
|
- [x] HTML
|
||||||
|
- [x] Tanstack
|
||||||
- [x] Vite
|
- [x] Vite
|
||||||
- [x] VueJS
|
- [x] VueJS
|
||||||
|
|
||||||
|
|||||||
22
html/README.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# HTML Example
|
||||||
|
|
||||||
|
This repository contains an example of HTML application that is deployed on Dokploy.
|
||||||
|
|
||||||
|
|
||||||
|
1. **Use Git Provider in Your Application**:
|
||||||
|
- Repository: `https://github.com/Dokploy/examples.git`
|
||||||
|
- Branch: `main`
|
||||||
|
- Build path: `/html`
|
||||||
|
- `Static` Build Type
|
||||||
|
|
||||||
|
3. **Click on Deploy**:
|
||||||
|
- Deploy your application by clicking the deploy button.
|
||||||
|
|
||||||
|
4. **Generate a Domain**:
|
||||||
|
- Click on generate domain button.
|
||||||
|
- A new domain will be generated for you.
|
||||||
|
- Set Port `80`
|
||||||
|
- You can use this domain to access your application.
|
||||||
|
|
||||||
|
|
||||||
|
If you need further assistance, join our [Discord server](https://discord.com/invite/2tBnJ3jDJc).
|
||||||
7
html/index.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Hello World</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello World</h1>
|
||||||
|
</body>
|
||||||
22
tanstack/.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.cache
|
||||||
|
.env
|
||||||
|
.vercel
|
||||||
|
.output
|
||||||
|
.vinxi
|
||||||
|
|
||||||
|
/build/
|
||||||
|
/api/
|
||||||
|
/server/build
|
||||||
|
/public/build
|
||||||
|
.vinxi
|
||||||
|
# Sentry Config File
|
||||||
|
.env.sentry-build-plugin
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
4
tanstack/.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
**/build
|
||||||
|
**/public
|
||||||
|
pnpm-lock.yaml
|
||||||
|
routeTree.gen.ts
|
||||||
11
tanstack/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files.watcherExclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
},
|
||||||
|
"files.readonlyInclude": {
|
||||||
|
"**/routeTree.gen.ts": true
|
||||||
|
}
|
||||||
|
}
|
||||||
22
tanstack/README.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Tanstack Example
|
||||||
|
|
||||||
|
This repository contains an example of Tanstack application that is deployed on Dokploy.
|
||||||
|
|
||||||
|
|
||||||
|
1. **Use Git Provider in Your Application**:
|
||||||
|
- Repository: `https://github.com/Dokploy/examples.git`
|
||||||
|
- Branch: `main`
|
||||||
|
- Build path: `/tanstack`
|
||||||
|
- use `Nixpacks` as builder
|
||||||
|
|
||||||
|
3. **Click on Deploy**:
|
||||||
|
- Deploy your application by clicking the deploy button.
|
||||||
|
|
||||||
|
4. **Generate a Domain**:
|
||||||
|
- Click on generate domain button.
|
||||||
|
- A new domain will be generated for you.
|
||||||
|
- Set Port `3000`
|
||||||
|
- You can use this domain to access your application.
|
||||||
|
|
||||||
|
|
||||||
|
If you need further assistance, join our [Discord server](https://discord.com/invite/2tBnJ3jDJc).
|
||||||
15
tanstack/app.config.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from '@tanstack/react-start/config'
|
||||||
|
import tsConfigPaths from 'vite-tsconfig-paths'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
tsr: {
|
||||||
|
appDirectory: 'src',
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
plugins: [
|
||||||
|
tsConfigPaths({
|
||||||
|
projects: ['./tsconfig.json'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
31
tanstack/package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "tanstack-start-example-basic",
|
||||||
|
"private": true,
|
||||||
|
"sideEffects": false,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vinxi dev",
|
||||||
|
"build": "vinxi build",
|
||||||
|
"start": "vinxi start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/react-router": "^1.115.0",
|
||||||
|
"@tanstack/react-router-devtools": "^1.115.0",
|
||||||
|
"@tanstack/react-start": "^1.115.1",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"redaxios": "^0.5.1",
|
||||||
|
"tailwind-merge": "^2.6.0",
|
||||||
|
"vinxi": "0.5.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.5.4",
|
||||||
|
"@types/react": "^19.0.8",
|
||||||
|
"@types/react-dom": "^19.0.3",
|
||||||
|
"postcss": "^8.5.1",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
5883
tanstack/pnpm-lock.yaml
generated
Normal file
6
tanstack/postcss.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
tanstack/public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
tanstack/public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tanstack/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
tanstack/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 832 B |
BIN
tanstack/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
tanstack/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
tanstack/public/favicon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
19
tanstack/public/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"short_name": "",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
6
tanstack/src/api.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import {
|
||||||
|
createStartAPIHandler,
|
||||||
|
defaultAPIFileRouteHandler,
|
||||||
|
} from '@tanstack/react-start/api'
|
||||||
|
|
||||||
|
export default createStartAPIHandler(defaultAPIFileRouteHandler)
|
||||||
8
tanstack/src/client.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="vinxi/types/client" />
|
||||||
|
import { hydrateRoot } from 'react-dom/client'
|
||||||
|
import { StartClient } from '@tanstack/react-start'
|
||||||
|
import { createRouter } from './router'
|
||||||
|
|
||||||
|
const router = createRouter()
|
||||||
|
|
||||||
|
hydrateRoot(document, <StartClient router={router} />)
|
||||||
53
tanstack/src/components/DefaultCatchBoundary.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
ErrorComponent,
|
||||||
|
Link,
|
||||||
|
rootRouteId,
|
||||||
|
useMatch,
|
||||||
|
useRouter,
|
||||||
|
} from '@tanstack/react-router'
|
||||||
|
import type { ErrorComponentProps } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
|
||||||
|
const router = useRouter()
|
||||||
|
const isRoot = useMatch({
|
||||||
|
strict: false,
|
||||||
|
select: (state) => state.id === rootRouteId,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.error('DefaultCatchBoundary Error:', error)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
|
||||||
|
<ErrorComponent error={error} />
|
||||||
|
<div className="flex gap-2 items-center flex-wrap">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
router.invalidate()
|
||||||
|
}}
|
||||||
|
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
|
||||||
|
>
|
||||||
|
Try Again
|
||||||
|
</button>
|
||||||
|
{isRoot ? (
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded text-white uppercase font-extrabold`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.history.back()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Go Back
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
25
tanstack/src/components/NotFound.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Link } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export function NotFound({ children }: { children?: any }) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-2 p-2">
|
||||||
|
<div className="text-gray-600 dark:text-gray-400">
|
||||||
|
{children || <p>The page you are looking for does not exist.</p>}
|
||||||
|
</div>
|
||||||
|
<p className="flex items-center gap-2 flex-wrap">
|
||||||
|
<button
|
||||||
|
onClick={() => window.history.back()}
|
||||||
|
className="bg-emerald-500 text-white px-2 py-1 rounded uppercase font-black text-sm"
|
||||||
|
>
|
||||||
|
Go back
|
||||||
|
</button>
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
className="bg-cyan-600 text-white px-2 py-1 rounded uppercase font-black text-sm"
|
||||||
|
>
|
||||||
|
Start Over
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
5
tanstack/src/components/PostError.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ErrorComponent, ErrorComponentProps } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export function PostErrorComponent({ error }: ErrorComponentProps) {
|
||||||
|
return <ErrorComponent error={error} />
|
||||||
|
}
|
||||||
5
tanstack/src/components/UserError.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { ErrorComponent, ErrorComponentProps } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export function UserErrorComponent({ error }: ErrorComponentProps) {
|
||||||
|
return <ErrorComponent error={error} />
|
||||||
|
}
|
||||||
6
tanstack/src/global-middleware.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { registerGlobalMiddleware } from '@tanstack/react-start'
|
||||||
|
import { logMiddleware } from './utils/loggingMiddleware'
|
||||||
|
|
||||||
|
registerGlobalMiddleware({
|
||||||
|
middleware: [logMiddleware],
|
||||||
|
})
|
||||||
479
tanstack/src/routeTree.gen.ts
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
|
||||||
|
// This file was automatically generated by TanStack Router.
|
||||||
|
// You should NOT make any changes in this file as it will be overwritten.
|
||||||
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
|
// Import Routes
|
||||||
|
|
||||||
|
import { Route as rootRoute } from './routes/__root'
|
||||||
|
import { Route as UsersImport } from './routes/users'
|
||||||
|
import { Route as RedirectImport } from './routes/redirect'
|
||||||
|
import { Route as PostsImport } from './routes/posts'
|
||||||
|
import { Route as DeferredImport } from './routes/deferred'
|
||||||
|
import { Route as PathlessLayoutImport } from './routes/_pathlessLayout'
|
||||||
|
import { Route as IndexImport } from './routes/index'
|
||||||
|
import { Route as UsersIndexImport } from './routes/users.index'
|
||||||
|
import { Route as PostsIndexImport } from './routes/posts.index'
|
||||||
|
import { Route as UsersUserIdImport } from './routes/users.$userId'
|
||||||
|
import { Route as PostsPostIdImport } from './routes/posts.$postId'
|
||||||
|
import { Route as PathlessLayoutNestedLayoutImport } from './routes/_pathlessLayout/_nested-layout'
|
||||||
|
import { Route as PostsPostIdDeepImport } from './routes/posts_.$postId.deep'
|
||||||
|
import { Route as PathlessLayoutNestedLayoutRouteBImport } from './routes/_pathlessLayout/_nested-layout/route-b'
|
||||||
|
import { Route as PathlessLayoutNestedLayoutRouteAImport } from './routes/_pathlessLayout/_nested-layout/route-a'
|
||||||
|
|
||||||
|
// Create/Update Routes
|
||||||
|
|
||||||
|
const UsersRoute = UsersImport.update({
|
||||||
|
id: '/users',
|
||||||
|
path: '/users',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const RedirectRoute = RedirectImport.update({
|
||||||
|
id: '/redirect',
|
||||||
|
path: '/redirect',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const PostsRoute = PostsImport.update({
|
||||||
|
id: '/posts',
|
||||||
|
path: '/posts',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const DeferredRoute = DeferredImport.update({
|
||||||
|
id: '/deferred',
|
||||||
|
path: '/deferred',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const PathlessLayoutRoute = PathlessLayoutImport.update({
|
||||||
|
id: '/_pathlessLayout',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const IndexRoute = IndexImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const UsersIndexRoute = UsersIndexImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => UsersRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const PostsIndexRoute = PostsIndexImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => PostsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const UsersUserIdRoute = UsersUserIdImport.update({
|
||||||
|
id: '/$userId',
|
||||||
|
path: '/$userId',
|
||||||
|
getParentRoute: () => UsersRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const PostsPostIdRoute = PostsPostIdImport.update({
|
||||||
|
id: '/$postId',
|
||||||
|
path: '/$postId',
|
||||||
|
getParentRoute: () => PostsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const PathlessLayoutNestedLayoutRoute = PathlessLayoutNestedLayoutImport.update(
|
||||||
|
{
|
||||||
|
id: '/_nested-layout',
|
||||||
|
getParentRoute: () => PathlessLayoutRoute,
|
||||||
|
} as any,
|
||||||
|
)
|
||||||
|
|
||||||
|
const PostsPostIdDeepRoute = PostsPostIdDeepImport.update({
|
||||||
|
id: '/posts_/$postId/deep',
|
||||||
|
path: '/posts/$postId/deep',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const PathlessLayoutNestedLayoutRouteBRoute =
|
||||||
|
PathlessLayoutNestedLayoutRouteBImport.update({
|
||||||
|
id: '/route-b',
|
||||||
|
path: '/route-b',
|
||||||
|
getParentRoute: () => PathlessLayoutNestedLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
const PathlessLayoutNestedLayoutRouteARoute =
|
||||||
|
PathlessLayoutNestedLayoutRouteAImport.update({
|
||||||
|
id: '/route-a',
|
||||||
|
path: '/route-a',
|
||||||
|
getParentRoute: () => PathlessLayoutNestedLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
// Populate the FileRoutesByPath interface
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface FileRoutesByPath {
|
||||||
|
'/': {
|
||||||
|
id: '/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/'
|
||||||
|
preLoaderRoute: typeof IndexImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
|
'/_pathlessLayout': {
|
||||||
|
id: '/_pathlessLayout'
|
||||||
|
path: ''
|
||||||
|
fullPath: ''
|
||||||
|
preLoaderRoute: typeof PathlessLayoutImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
|
'/deferred': {
|
||||||
|
id: '/deferred'
|
||||||
|
path: '/deferred'
|
||||||
|
fullPath: '/deferred'
|
||||||
|
preLoaderRoute: typeof DeferredImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
|
'/posts': {
|
||||||
|
id: '/posts'
|
||||||
|
path: '/posts'
|
||||||
|
fullPath: '/posts'
|
||||||
|
preLoaderRoute: typeof PostsImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
|
'/redirect': {
|
||||||
|
id: '/redirect'
|
||||||
|
path: '/redirect'
|
||||||
|
fullPath: '/redirect'
|
||||||
|
preLoaderRoute: typeof RedirectImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
|
'/users': {
|
||||||
|
id: '/users'
|
||||||
|
path: '/users'
|
||||||
|
fullPath: '/users'
|
||||||
|
preLoaderRoute: typeof UsersImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
|
'/_pathlessLayout/_nested-layout': {
|
||||||
|
id: '/_pathlessLayout/_nested-layout'
|
||||||
|
path: ''
|
||||||
|
fullPath: ''
|
||||||
|
preLoaderRoute: typeof PathlessLayoutNestedLayoutImport
|
||||||
|
parentRoute: typeof PathlessLayoutImport
|
||||||
|
}
|
||||||
|
'/posts/$postId': {
|
||||||
|
id: '/posts/$postId'
|
||||||
|
path: '/$postId'
|
||||||
|
fullPath: '/posts/$postId'
|
||||||
|
preLoaderRoute: typeof PostsPostIdImport
|
||||||
|
parentRoute: typeof PostsImport
|
||||||
|
}
|
||||||
|
'/users/$userId': {
|
||||||
|
id: '/users/$userId'
|
||||||
|
path: '/$userId'
|
||||||
|
fullPath: '/users/$userId'
|
||||||
|
preLoaderRoute: typeof UsersUserIdImport
|
||||||
|
parentRoute: typeof UsersImport
|
||||||
|
}
|
||||||
|
'/posts/': {
|
||||||
|
id: '/posts/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/posts/'
|
||||||
|
preLoaderRoute: typeof PostsIndexImport
|
||||||
|
parentRoute: typeof PostsImport
|
||||||
|
}
|
||||||
|
'/users/': {
|
||||||
|
id: '/users/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/users/'
|
||||||
|
preLoaderRoute: typeof UsersIndexImport
|
||||||
|
parentRoute: typeof UsersImport
|
||||||
|
}
|
||||||
|
'/_pathlessLayout/_nested-layout/route-a': {
|
||||||
|
id: '/_pathlessLayout/_nested-layout/route-a'
|
||||||
|
path: '/route-a'
|
||||||
|
fullPath: '/route-a'
|
||||||
|
preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteAImport
|
||||||
|
parentRoute: typeof PathlessLayoutNestedLayoutImport
|
||||||
|
}
|
||||||
|
'/_pathlessLayout/_nested-layout/route-b': {
|
||||||
|
id: '/_pathlessLayout/_nested-layout/route-b'
|
||||||
|
path: '/route-b'
|
||||||
|
fullPath: '/route-b'
|
||||||
|
preLoaderRoute: typeof PathlessLayoutNestedLayoutRouteBImport
|
||||||
|
parentRoute: typeof PathlessLayoutNestedLayoutImport
|
||||||
|
}
|
||||||
|
'/posts_/$postId/deep': {
|
||||||
|
id: '/posts_/$postId/deep'
|
||||||
|
path: '/posts/$postId/deep'
|
||||||
|
fullPath: '/posts/$postId/deep'
|
||||||
|
preLoaderRoute: typeof PostsPostIdDeepImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and export the route tree
|
||||||
|
|
||||||
|
interface PathlessLayoutNestedLayoutRouteChildren {
|
||||||
|
PathlessLayoutNestedLayoutRouteARoute: typeof PathlessLayoutNestedLayoutRouteARoute
|
||||||
|
PathlessLayoutNestedLayoutRouteBRoute: typeof PathlessLayoutNestedLayoutRouteBRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const PathlessLayoutNestedLayoutRouteChildren: PathlessLayoutNestedLayoutRouteChildren =
|
||||||
|
{
|
||||||
|
PathlessLayoutNestedLayoutRouteARoute:
|
||||||
|
PathlessLayoutNestedLayoutRouteARoute,
|
||||||
|
PathlessLayoutNestedLayoutRouteBRoute:
|
||||||
|
PathlessLayoutNestedLayoutRouteBRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PathlessLayoutNestedLayoutRouteWithChildren =
|
||||||
|
PathlessLayoutNestedLayoutRoute._addFileChildren(
|
||||||
|
PathlessLayoutNestedLayoutRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface PathlessLayoutRouteChildren {
|
||||||
|
PathlessLayoutNestedLayoutRoute: typeof PathlessLayoutNestedLayoutRouteWithChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
const PathlessLayoutRouteChildren: PathlessLayoutRouteChildren = {
|
||||||
|
PathlessLayoutNestedLayoutRoute: PathlessLayoutNestedLayoutRouteWithChildren,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PathlessLayoutRouteWithChildren = PathlessLayoutRoute._addFileChildren(
|
||||||
|
PathlessLayoutRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
|
interface PostsRouteChildren {
|
||||||
|
PostsPostIdRoute: typeof PostsPostIdRoute
|
||||||
|
PostsIndexRoute: typeof PostsIndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostsRouteChildren: PostsRouteChildren = {
|
||||||
|
PostsPostIdRoute: PostsPostIdRoute,
|
||||||
|
PostsIndexRoute: PostsIndexRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const PostsRouteWithChildren = PostsRoute._addFileChildren(PostsRouteChildren)
|
||||||
|
|
||||||
|
interface UsersRouteChildren {
|
||||||
|
UsersUserIdRoute: typeof UsersUserIdRoute
|
||||||
|
UsersIndexRoute: typeof UsersIndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const UsersRouteChildren: UsersRouteChildren = {
|
||||||
|
UsersUserIdRoute: UsersUserIdRoute,
|
||||||
|
UsersIndexRoute: UsersIndexRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const UsersRouteWithChildren = UsersRoute._addFileChildren(UsersRouteChildren)
|
||||||
|
|
||||||
|
export interface FileRoutesByFullPath {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
'': typeof PathlessLayoutNestedLayoutRouteWithChildren
|
||||||
|
'/deferred': typeof DeferredRoute
|
||||||
|
'/posts': typeof PostsRouteWithChildren
|
||||||
|
'/redirect': typeof RedirectRoute
|
||||||
|
'/users': typeof UsersRouteWithChildren
|
||||||
|
'/posts/$postId': typeof PostsPostIdRoute
|
||||||
|
'/users/$userId': typeof UsersUserIdRoute
|
||||||
|
'/posts/': typeof PostsIndexRoute
|
||||||
|
'/users/': typeof UsersIndexRoute
|
||||||
|
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
|
||||||
|
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
|
||||||
|
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileRoutesByTo {
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
'': typeof PathlessLayoutNestedLayoutRouteWithChildren
|
||||||
|
'/deferred': typeof DeferredRoute
|
||||||
|
'/redirect': typeof RedirectRoute
|
||||||
|
'/posts/$postId': typeof PostsPostIdRoute
|
||||||
|
'/users/$userId': typeof UsersUserIdRoute
|
||||||
|
'/posts': typeof PostsIndexRoute
|
||||||
|
'/users': typeof UsersIndexRoute
|
||||||
|
'/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
|
||||||
|
'/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
|
||||||
|
'/posts/$postId/deep': typeof PostsPostIdDeepRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileRoutesById {
|
||||||
|
__root__: typeof rootRoute
|
||||||
|
'/': typeof IndexRoute
|
||||||
|
'/_pathlessLayout': typeof PathlessLayoutRouteWithChildren
|
||||||
|
'/deferred': typeof DeferredRoute
|
||||||
|
'/posts': typeof PostsRouteWithChildren
|
||||||
|
'/redirect': typeof RedirectRoute
|
||||||
|
'/users': typeof UsersRouteWithChildren
|
||||||
|
'/_pathlessLayout/_nested-layout': typeof PathlessLayoutNestedLayoutRouteWithChildren
|
||||||
|
'/posts/$postId': typeof PostsPostIdRoute
|
||||||
|
'/users/$userId': typeof UsersUserIdRoute
|
||||||
|
'/posts/': typeof PostsIndexRoute
|
||||||
|
'/users/': typeof UsersIndexRoute
|
||||||
|
'/_pathlessLayout/_nested-layout/route-a': typeof PathlessLayoutNestedLayoutRouteARoute
|
||||||
|
'/_pathlessLayout/_nested-layout/route-b': typeof PathlessLayoutNestedLayoutRouteBRoute
|
||||||
|
'/posts_/$postId/deep': typeof PostsPostIdDeepRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileRouteTypes {
|
||||||
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
|
fullPaths:
|
||||||
|
| '/'
|
||||||
|
| ''
|
||||||
|
| '/deferred'
|
||||||
|
| '/posts'
|
||||||
|
| '/redirect'
|
||||||
|
| '/users'
|
||||||
|
| '/posts/$postId'
|
||||||
|
| '/users/$userId'
|
||||||
|
| '/posts/'
|
||||||
|
| '/users/'
|
||||||
|
| '/route-a'
|
||||||
|
| '/route-b'
|
||||||
|
| '/posts/$postId/deep'
|
||||||
|
fileRoutesByTo: FileRoutesByTo
|
||||||
|
to:
|
||||||
|
| '/'
|
||||||
|
| ''
|
||||||
|
| '/deferred'
|
||||||
|
| '/redirect'
|
||||||
|
| '/posts/$postId'
|
||||||
|
| '/users/$userId'
|
||||||
|
| '/posts'
|
||||||
|
| '/users'
|
||||||
|
| '/route-a'
|
||||||
|
| '/route-b'
|
||||||
|
| '/posts/$postId/deep'
|
||||||
|
id:
|
||||||
|
| '__root__'
|
||||||
|
| '/'
|
||||||
|
| '/_pathlessLayout'
|
||||||
|
| '/deferred'
|
||||||
|
| '/posts'
|
||||||
|
| '/redirect'
|
||||||
|
| '/users'
|
||||||
|
| '/_pathlessLayout/_nested-layout'
|
||||||
|
| '/posts/$postId'
|
||||||
|
| '/users/$userId'
|
||||||
|
| '/posts/'
|
||||||
|
| '/users/'
|
||||||
|
| '/_pathlessLayout/_nested-layout/route-a'
|
||||||
|
| '/_pathlessLayout/_nested-layout/route-b'
|
||||||
|
| '/posts_/$postId/deep'
|
||||||
|
fileRoutesById: FileRoutesById
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RootRouteChildren {
|
||||||
|
IndexRoute: typeof IndexRoute
|
||||||
|
PathlessLayoutRoute: typeof PathlessLayoutRouteWithChildren
|
||||||
|
DeferredRoute: typeof DeferredRoute
|
||||||
|
PostsRoute: typeof PostsRouteWithChildren
|
||||||
|
RedirectRoute: typeof RedirectRoute
|
||||||
|
UsersRoute: typeof UsersRouteWithChildren
|
||||||
|
PostsPostIdDeepRoute: typeof PostsPostIdDeepRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
|
IndexRoute: IndexRoute,
|
||||||
|
PathlessLayoutRoute: PathlessLayoutRouteWithChildren,
|
||||||
|
DeferredRoute: DeferredRoute,
|
||||||
|
PostsRoute: PostsRouteWithChildren,
|
||||||
|
RedirectRoute: RedirectRoute,
|
||||||
|
UsersRoute: UsersRouteWithChildren,
|
||||||
|
PostsPostIdDeepRoute: PostsPostIdDeepRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routeTree = rootRoute
|
||||||
|
._addFileChildren(rootRouteChildren)
|
||||||
|
._addFileTypes<FileRouteTypes>()
|
||||||
|
|
||||||
|
/* ROUTE_MANIFEST_START
|
||||||
|
{
|
||||||
|
"routes": {
|
||||||
|
"__root__": {
|
||||||
|
"filePath": "__root.tsx",
|
||||||
|
"children": [
|
||||||
|
"/",
|
||||||
|
"/_pathlessLayout",
|
||||||
|
"/deferred",
|
||||||
|
"/posts",
|
||||||
|
"/redirect",
|
||||||
|
"/users",
|
||||||
|
"/posts_/$postId/deep"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/": {
|
||||||
|
"filePath": "index.tsx"
|
||||||
|
},
|
||||||
|
"/_pathlessLayout": {
|
||||||
|
"filePath": "_pathlessLayout.tsx",
|
||||||
|
"children": [
|
||||||
|
"/_pathlessLayout/_nested-layout"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/deferred": {
|
||||||
|
"filePath": "deferred.tsx"
|
||||||
|
},
|
||||||
|
"/posts": {
|
||||||
|
"filePath": "posts.tsx",
|
||||||
|
"children": [
|
||||||
|
"/posts/$postId",
|
||||||
|
"/posts/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/redirect": {
|
||||||
|
"filePath": "redirect.tsx"
|
||||||
|
},
|
||||||
|
"/users": {
|
||||||
|
"filePath": "users.tsx",
|
||||||
|
"children": [
|
||||||
|
"/users/$userId",
|
||||||
|
"/users/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/_pathlessLayout/_nested-layout": {
|
||||||
|
"filePath": "_pathlessLayout/_nested-layout.tsx",
|
||||||
|
"parent": "/_pathlessLayout",
|
||||||
|
"children": [
|
||||||
|
"/_pathlessLayout/_nested-layout/route-a",
|
||||||
|
"/_pathlessLayout/_nested-layout/route-b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"/posts/$postId": {
|
||||||
|
"filePath": "posts.$postId.tsx",
|
||||||
|
"parent": "/posts"
|
||||||
|
},
|
||||||
|
"/users/$userId": {
|
||||||
|
"filePath": "users.$userId.tsx",
|
||||||
|
"parent": "/users"
|
||||||
|
},
|
||||||
|
"/posts/": {
|
||||||
|
"filePath": "posts.index.tsx",
|
||||||
|
"parent": "/posts"
|
||||||
|
},
|
||||||
|
"/users/": {
|
||||||
|
"filePath": "users.index.tsx",
|
||||||
|
"parent": "/users"
|
||||||
|
},
|
||||||
|
"/_pathlessLayout/_nested-layout/route-a": {
|
||||||
|
"filePath": "_pathlessLayout/_nested-layout/route-a.tsx",
|
||||||
|
"parent": "/_pathlessLayout/_nested-layout"
|
||||||
|
},
|
||||||
|
"/_pathlessLayout/_nested-layout/route-b": {
|
||||||
|
"filePath": "_pathlessLayout/_nested-layout/route-b.tsx",
|
||||||
|
"parent": "/_pathlessLayout/_nested-layout"
|
||||||
|
},
|
||||||
|
"/posts_/$postId/deep": {
|
||||||
|
"filePath": "posts_.$postId.deep.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ROUTE_MANIFEST_END */
|
||||||
22
tanstack/src/router.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { createRouter as createTanStackRouter } from '@tanstack/react-router'
|
||||||
|
import { routeTree } from './routeTree.gen'
|
||||||
|
import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
|
||||||
|
import { NotFound } from './components/NotFound'
|
||||||
|
|
||||||
|
export function createRouter() {
|
||||||
|
const router = createTanStackRouter({
|
||||||
|
routeTree,
|
||||||
|
defaultPreload: 'intent',
|
||||||
|
defaultErrorComponent: DefaultCatchBoundary,
|
||||||
|
defaultNotFoundComponent: () => <NotFound />,
|
||||||
|
scrollRestoration: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@tanstack/react-router' {
|
||||||
|
interface Register {
|
||||||
|
router: ReturnType<typeof createRouter>
|
||||||
|
}
|
||||||
|
}
|
||||||
139
tanstack/src/routes/__root.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import {
|
||||||
|
HeadContent,
|
||||||
|
Link,
|
||||||
|
Outlet,
|
||||||
|
Scripts,
|
||||||
|
createRootRoute,
|
||||||
|
} from '@tanstack/react-router'
|
||||||
|
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||||
|
import * as React from 'react'
|
||||||
|
import { DefaultCatchBoundary } from '~/components/DefaultCatchBoundary'
|
||||||
|
import { NotFound } from '~/components/NotFound'
|
||||||
|
import appCss from '~/styles/app.css?url'
|
||||||
|
import { seo } from '~/utils/seo'
|
||||||
|
|
||||||
|
export const Route = createRootRoute({
|
||||||
|
head: () => ({
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
charSet: 'utf-8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'viewport',
|
||||||
|
content: 'width=device-width, initial-scale=1',
|
||||||
|
},
|
||||||
|
...seo({
|
||||||
|
title:
|
||||||
|
'TanStack Start | Type-Safe, Client-First, Full-Stack React Framework',
|
||||||
|
description: `TanStack Start is a type-safe, client-first, full-stack React framework. `,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
{ rel: 'stylesheet', href: appCss },
|
||||||
|
{
|
||||||
|
rel: 'apple-touch-icon',
|
||||||
|
sizes: '180x180',
|
||||||
|
href: '/apple-touch-icon.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '32x32',
|
||||||
|
href: '/favicon-32x32.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '16x16',
|
||||||
|
href: '/favicon-16x16.png',
|
||||||
|
},
|
||||||
|
{ rel: 'manifest', href: '/site.webmanifest', color: '#fffff' },
|
||||||
|
{ rel: 'icon', href: '/favicon.ico' },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
errorComponent: (props) => {
|
||||||
|
return (
|
||||||
|
<RootDocument>
|
||||||
|
<DefaultCatchBoundary {...props} />
|
||||||
|
</RootDocument>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
notFoundComponent: () => <NotFound />,
|
||||||
|
component: RootComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function RootComponent() {
|
||||||
|
return (
|
||||||
|
<RootDocument>
|
||||||
|
<Outlet />
|
||||||
|
</RootDocument>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<HeadContent />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div className="p-2 flex gap-2 text-lg">
|
||||||
|
<Link
|
||||||
|
to="/"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
activeOptions={{ exact: true }}
|
||||||
|
>
|
||||||
|
Home
|
||||||
|
</Link>{' '}
|
||||||
|
<Link
|
||||||
|
to="/posts"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Posts
|
||||||
|
</Link>{' '}
|
||||||
|
<Link
|
||||||
|
to="/users"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Users
|
||||||
|
</Link>{' '}
|
||||||
|
<Link
|
||||||
|
to="/route-a"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Pathless Layout
|
||||||
|
</Link>{' '}
|
||||||
|
<Link
|
||||||
|
to="/deferred"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Deferred
|
||||||
|
</Link>{' '}
|
||||||
|
<Link
|
||||||
|
// @ts-expect-error
|
||||||
|
to="/this-route-does-not-exist"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
This Route Does Not Exist
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
{children}
|
||||||
|
<TanStackRouterDevtools position="bottom-right" />
|
||||||
|
<Scripts />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
16
tanstack/src/routes/_pathlessLayout.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Outlet, createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_pathlessLayout')({
|
||||||
|
component: LayoutComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function LayoutComponent() {
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<div className="border-b">I'm a layout</div>
|
||||||
|
<div>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
tanstack/src/routes/_pathlessLayout/_nested-layout.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_pathlessLayout/_nested-layout')({
|
||||||
|
component: LayoutComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function LayoutComponent() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>I'm a nested layout</div>
|
||||||
|
<div className="flex gap-2 border-b">
|
||||||
|
<Link
|
||||||
|
to="/route-a"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Go to route A
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/route-b"
|
||||||
|
activeProps={{
|
||||||
|
className: 'font-bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Go to route B
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-a')(
|
||||||
|
{
|
||||||
|
component: LayoutAComponent,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function LayoutAComponent() {
|
||||||
|
return <div>I'm A!</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-b')(
|
||||||
|
{
|
||||||
|
component: LayoutBComponent,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
function LayoutBComponent() {
|
||||||
|
return <div>I'm B!</div>
|
||||||
|
}
|
||||||
24
tanstack/src/routes/api/users.$id.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { json } from '@tanstack/react-start'
|
||||||
|
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
||||||
|
import axios from 'redaxios'
|
||||||
|
import type { User } from '../../utils/users'
|
||||||
|
|
||||||
|
export const APIRoute = createAPIFileRoute('/api/users/$id')({
|
||||||
|
GET: async ({ request, params }) => {
|
||||||
|
console.info(`Fetching users by id=${params.id}... @`, request.url)
|
||||||
|
try {
|
||||||
|
const res = await axios.get<User>(
|
||||||
|
'https://jsonplaceholder.typicode.com/users/' + params.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return json({
|
||||||
|
id: res.data.id,
|
||||||
|
name: res.data.name,
|
||||||
|
email: res.data.email,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return json({ error: 'User not found' }, { status: 404 })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
17
tanstack/src/routes/api/users.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { json } from '@tanstack/react-start'
|
||||||
|
import { createAPIFileRoute } from '@tanstack/react-start/api'
|
||||||
|
import axios from 'redaxios'
|
||||||
|
import type { User } from '../../utils/users'
|
||||||
|
|
||||||
|
export const APIRoute = createAPIFileRoute('/api/users')({
|
||||||
|
GET: async ({ request }) => {
|
||||||
|
console.info('Fetching users... @', request.url)
|
||||||
|
const res = await axios.get<Array<User>>(
|
||||||
|
'https://jsonplaceholder.typicode.com/users',
|
||||||
|
)
|
||||||
|
|
||||||
|
const list = res.data.slice(0, 10)
|
||||||
|
|
||||||
|
return json(list.map((u) => ({ id: u.id, name: u.name, email: u.email })))
|
||||||
|
},
|
||||||
|
})
|
||||||
62
tanstack/src/routes/deferred.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Await, createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { createServerFn } from '@tanstack/react-start'
|
||||||
|
import { Suspense, useState } from 'react'
|
||||||
|
|
||||||
|
const personServerFn = createServerFn({ method: 'GET' })
|
||||||
|
.validator((d: string) => d)
|
||||||
|
.handler(({ data: name }) => {
|
||||||
|
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
||||||
|
})
|
||||||
|
|
||||||
|
const slowServerFn = createServerFn({ method: 'GET' })
|
||||||
|
.validator((d: string) => d)
|
||||||
|
.handler(async ({ data: name }) => {
|
||||||
|
await new Promise((r) => setTimeout(r, 1000))
|
||||||
|
return { name, randomNumber: Math.floor(Math.random() * 100) }
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/deferred')({
|
||||||
|
loader: async () => {
|
||||||
|
return {
|
||||||
|
deferredStuff: new Promise<string>((r) =>
|
||||||
|
setTimeout(() => r('Hello deferred!'), 2000),
|
||||||
|
),
|
||||||
|
deferredPerson: slowServerFn({ data: 'Tanner Linsley' }),
|
||||||
|
person: await personServerFn({ data: 'John Doe' }),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
component: Deferred,
|
||||||
|
})
|
||||||
|
|
||||||
|
function Deferred() {
|
||||||
|
const [count, setCount] = useState(0)
|
||||||
|
const { deferredStuff, deferredPerson, person } = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<div data-testid="regular-person">
|
||||||
|
{person.name} - {person.randomNumber}
|
||||||
|
</div>
|
||||||
|
<Suspense fallback={<div>Loading person...</div>}>
|
||||||
|
<Await
|
||||||
|
promise={deferredPerson}
|
||||||
|
children={(data) => (
|
||||||
|
<div data-testid="deferred-person">
|
||||||
|
{data.name} - {data.randomNumber}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
<Suspense fallback={<div>Loading stuff...</div>}>
|
||||||
|
<Await
|
||||||
|
promise={deferredStuff}
|
||||||
|
children={(data) => <h3 data-testid="deferred-stuff">{data}</h3>}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
<div>Count: {count}</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={() => setCount(count + 1)}>Increment</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
13
tanstack/src/routes/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/')({
|
||||||
|
component: Home,
|
||||||
|
})
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<h3>Welcome Home!!!</h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
34
tanstack/src/routes/posts.$postId.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Link, createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { fetchPost } from '../utils/posts'
|
||||||
|
import { NotFound } from '~/components/NotFound'
|
||||||
|
import { PostErrorComponent } from '~/components/PostError'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/posts/$postId')({
|
||||||
|
loader: ({ params: { postId } }) => fetchPost({ data: postId }),
|
||||||
|
errorComponent: PostErrorComponent,
|
||||||
|
component: PostComponent,
|
||||||
|
notFoundComponent: () => {
|
||||||
|
return <NotFound>Post not found</NotFound>
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function PostComponent() {
|
||||||
|
const post = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
||||||
|
<div className="text-sm">{post.body}</div>
|
||||||
|
<Link
|
||||||
|
to="/posts/$postId/deep"
|
||||||
|
params={{
|
||||||
|
postId: post.id,
|
||||||
|
}}
|
||||||
|
activeProps={{ className: 'text-black font-bold' }}
|
||||||
|
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||||
|
>
|
||||||
|
Deep View
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
tanstack/src/routes/posts.index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/posts/')({
|
||||||
|
component: PostsIndexComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function PostsIndexComponent() {
|
||||||
|
return <div>Select a post.</div>
|
||||||
|
}
|
||||||
38
tanstack/src/routes/posts.route.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { fetchPosts } from '../utils/posts'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/posts')({
|
||||||
|
loader: async () => fetchPosts(),
|
||||||
|
component: PostsLayoutComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function PostsLayoutComponent() {
|
||||||
|
const posts = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-2 flex gap-2">
|
||||||
|
<ul className="list-disc pl-4">
|
||||||
|
{[...posts, { id: 'i-do-not-exist', title: 'Non-existent Post' }].map(
|
||||||
|
(post) => {
|
||||||
|
return (
|
||||||
|
<li key={post.id} className="whitespace-nowrap">
|
||||||
|
<Link
|
||||||
|
to="/posts/$postId"
|
||||||
|
params={{
|
||||||
|
postId: post.id,
|
||||||
|
}}
|
||||||
|
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||||
|
activeProps={{ className: 'text-black font-bold' }}
|
||||||
|
>
|
||||||
|
<div>{post.title.substring(0, 20)}</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
tanstack/src/routes/posts_.$postId.deep.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Link, createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { fetchPost } from '../utils/posts'
|
||||||
|
import { PostErrorComponent } from '~/components/PostError'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/posts_/$postId/deep')({
|
||||||
|
loader: async ({ params: { postId } }) =>
|
||||||
|
fetchPost({
|
||||||
|
data: postId,
|
||||||
|
}),
|
||||||
|
errorComponent: PostErrorComponent,
|
||||||
|
component: PostDeepComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function PostDeepComponent() {
|
||||||
|
const post = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-2 space-y-2">
|
||||||
|
<Link
|
||||||
|
to="/posts"
|
||||||
|
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||||
|
>
|
||||||
|
← All Posts
|
||||||
|
</Link>
|
||||||
|
<h4 className="text-xl font-bold underline">{post.title}</h4>
|
||||||
|
<div className="text-sm">{post.body}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
tanstack/src/routes/redirect.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/redirect')({
|
||||||
|
beforeLoad: async () => {
|
||||||
|
throw redirect({
|
||||||
|
to: '/posts',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
33
tanstack/src/routes/users.$userId.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import axios from 'redaxios'
|
||||||
|
import type { User } from '~/utils/users'
|
||||||
|
import { DEPLOY_URL } from '~/utils/users'
|
||||||
|
import { NotFound } from '~/components/NotFound'
|
||||||
|
import { UserErrorComponent } from '~/components/UserError'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/users/$userId')({
|
||||||
|
loader: async ({ params: { userId } }) => {
|
||||||
|
return await axios
|
||||||
|
.get<User>(DEPLOY_URL + '/api/users/' + userId)
|
||||||
|
.then((r) => r.data)
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Failed to fetch user')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
errorComponent: UserErrorComponent,
|
||||||
|
component: UserComponent,
|
||||||
|
notFoundComponent: () => {
|
||||||
|
return <NotFound>User not found</NotFound>
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function UserComponent() {
|
||||||
|
const user = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="text-xl font-bold underline">{user.name}</h4>
|
||||||
|
<div className="text-sm">{user.email}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
9
tanstack/src/routes/users.index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/users/')({
|
||||||
|
component: UsersIndexComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function UsersIndexComponent() {
|
||||||
|
return <div>Select a user.</div>
|
||||||
|
}
|
||||||
48
tanstack/src/routes/users.route.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'
|
||||||
|
import axios from 'redaxios'
|
||||||
|
import { DEPLOY_URL } from '../utils/users'
|
||||||
|
import type { User } from '../utils/users'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/users')({
|
||||||
|
loader: async () => {
|
||||||
|
return await axios
|
||||||
|
.get<Array<User>>(DEPLOY_URL + '/api/users')
|
||||||
|
.then((r) => r.data)
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Failed to fetch users')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
component: UsersLayoutComponent,
|
||||||
|
})
|
||||||
|
|
||||||
|
function UsersLayoutComponent() {
|
||||||
|
const users = Route.useLoaderData()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-2 flex gap-2">
|
||||||
|
<ul className="list-disc pl-4">
|
||||||
|
{[
|
||||||
|
...users,
|
||||||
|
{ id: 'i-do-not-exist', name: 'Non-existent User', email: '' },
|
||||||
|
].map((user) => {
|
||||||
|
return (
|
||||||
|
<li key={user.id} className="whitespace-nowrap">
|
||||||
|
<Link
|
||||||
|
to="/users/$userId"
|
||||||
|
params={{
|
||||||
|
userId: String(user.id),
|
||||||
|
}}
|
||||||
|
className="block py-1 text-blue-800 hover:text-blue-600"
|
||||||
|
activeProps={{ className: 'text-black font-bold' }}
|
||||||
|
>
|
||||||
|
<div>{user.name}</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<hr />
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
13
tanstack/src/ssr.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/// <reference types="vinxi/types/server" />
|
||||||
|
import {
|
||||||
|
createStartHandler,
|
||||||
|
defaultStreamHandler,
|
||||||
|
} from '@tanstack/react-start/server'
|
||||||
|
import { getRouterManifest } from '@tanstack/react-start/router-manifest'
|
||||||
|
|
||||||
|
import { createRouter } from './router'
|
||||||
|
|
||||||
|
export default createStartHandler({
|
||||||
|
createRouter,
|
||||||
|
getRouterManifest,
|
||||||
|
})(defaultStreamHandler)
|
||||||
22
tanstack/src/styles/app.css
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
html {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
@apply border-gray-200 dark:border-gray-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
@apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.using-mouse * {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
tanstack/src/utils/loggingMiddleware.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { createMiddleware } from '@tanstack/react-start'
|
||||||
|
|
||||||
|
const preLogMiddleware = createMiddleware()
|
||||||
|
.client(async (ctx) => {
|
||||||
|
const clientTime = new Date()
|
||||||
|
|
||||||
|
return ctx.next({
|
||||||
|
context: {
|
||||||
|
clientTime,
|
||||||
|
},
|
||||||
|
sendContext: {
|
||||||
|
clientTime,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.server(async (ctx) => {
|
||||||
|
const serverTime = new Date()
|
||||||
|
|
||||||
|
return ctx.next({
|
||||||
|
sendContext: {
|
||||||
|
serverTime,
|
||||||
|
durationToServer:
|
||||||
|
serverTime.getTime() - ctx.context.clientTime.getTime(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const logMiddleware = createMiddleware()
|
||||||
|
.middleware([preLogMiddleware])
|
||||||
|
.client(async (ctx) => {
|
||||||
|
const res = await ctx.next()
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
console.log('Client Req/Res:', {
|
||||||
|
duration: res.context.clientTime.getTime() - now.getTime(),
|
||||||
|
durationToServer: res.context.durationToServer,
|
||||||
|
durationFromServer: now.getTime() - res.context.serverTime.getTime(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return res
|
||||||
|
})
|
||||||
36
tanstack/src/utils/posts.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { notFound } from '@tanstack/react-router'
|
||||||
|
import { createServerFn } from '@tanstack/react-start'
|
||||||
|
import axios from 'redaxios'
|
||||||
|
|
||||||
|
export type PostType = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
body: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchPost = createServerFn({ method: 'GET' })
|
||||||
|
.validator((d: string) => d)
|
||||||
|
.handler(async ({ data }) => {
|
||||||
|
console.info(`Fetching post with id ${data}...`)
|
||||||
|
const post = await axios
|
||||||
|
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${data}`)
|
||||||
|
.then((r) => r.data)
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
if (err.status === 404) {
|
||||||
|
throw notFound()
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
return post
|
||||||
|
})
|
||||||
|
|
||||||
|
export const fetchPosts = createServerFn({ method: 'GET' }).handler(
|
||||||
|
async () => {
|
||||||
|
console.info('Fetching posts...')
|
||||||
|
return axios
|
||||||
|
.get<Array<PostType>>('https://jsonplaceholder.typicode.com/posts')
|
||||||
|
.then((r) => r.data.slice(0, 10))
|
||||||
|
},
|
||||||
|
)
|
||||||
33
tanstack/src/utils/seo.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export const seo = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
keywords,
|
||||||
|
image,
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
image?: string
|
||||||
|
keywords?: string
|
||||||
|
}) => {
|
||||||
|
const tags = [
|
||||||
|
{ title },
|
||||||
|
{ name: 'description', content: description },
|
||||||
|
{ name: 'keywords', content: keywords },
|
||||||
|
{ name: 'twitter:title', content: title },
|
||||||
|
{ name: 'twitter:description', content: description },
|
||||||
|
{ name: 'twitter:creator', content: '@tannerlinsley' },
|
||||||
|
{ name: 'twitter:site', content: '@tannerlinsley' },
|
||||||
|
{ name: 'og:type', content: 'website' },
|
||||||
|
{ name: 'og:title', content: title },
|
||||||
|
{ name: 'og:description', content: description },
|
||||||
|
...(image
|
||||||
|
? [
|
||||||
|
{ name: 'twitter:image', content: image },
|
||||||
|
{ name: 'twitter:card', content: 'summary_large_image' },
|
||||||
|
{ name: 'og:image', content: image },
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]
|
||||||
|
|
||||||
|
return tags
|
||||||
|
}
|
||||||
7
tanstack/src/utils/users.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export type User = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEPLOY_URL = 'http://localhost:3000'
|
||||||
4
tanstack/tailwind.config.mjs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||||
|
}
|
||||||
22
tanstack/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"allowJs": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||