Merge pull request #14 from Dokploy/feat/new-examples

feat: add initial HTML example and README documentation
This commit is contained in:
Mauricio Siu
2025-04-06 03:58:00 -06:00
committed by GitHub
53 changed files with 7367 additions and 0 deletions

View File

@@ -20,6 +20,8 @@ This repository contains examples of how to deploy applications using Dokploy.
- [x] Svelte
- [x] T3 App
- [x] Turborepo
- [x] HTML
- [x] Tanstack
- [x] Vite
- [x] VueJS

22
html/README.md Normal file
View 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
View File

@@ -0,0 +1,7 @@
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
</body>

22
tanstack/.gitignore vendored Normal file
View 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
View File

@@ -0,0 +1,4 @@
**/build
**/public
pnpm-lock.yaml
routeTree.gen.ts

11
tanstack/.vscode/settings.json vendored Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
tanstack/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
tanstack/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View 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
View File

@@ -0,0 +1,6 @@
import {
createStartAPIHandler,
defaultAPIFileRouteHandler,
} from '@tanstack/react-start/api'
export default createStartAPIHandler(defaultAPIFileRouteHandler)

8
tanstack/src/client.tsx Normal file
View 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} />)

View 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>
)
}

View 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>
)
}

View File

@@ -0,0 +1,5 @@
import { ErrorComponent, ErrorComponentProps } from '@tanstack/react-router'
export function PostErrorComponent({ error }: ErrorComponentProps) {
return <ErrorComponent error={error} />
}

View File

@@ -0,0 +1,5 @@
import { ErrorComponent, ErrorComponentProps } from '@tanstack/react-router'
export function UserErrorComponent({ error }: ErrorComponentProps) {
return <ErrorComponent error={error} />
}

View File

@@ -0,0 +1,6 @@
import { registerGlobalMiddleware } from '@tanstack/react-start'
import { logMiddleware } from './utils/loggingMiddleware'
registerGlobalMiddleware({
middleware: [logMiddleware],
})

View 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
View 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>
}
}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View File

@@ -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>
}

View File

@@ -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>
}

View 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 })
}
},
})

View 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 })))
},
})

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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>
}

View 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>
)
}

View 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>
)
}

View File

@@ -0,0 +1,9 @@
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/redirect')({
beforeLoad: async () => {
throw redirect({
to: '/posts',
})
},
})

View 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>
)
}

View 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>
}

View 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
View 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)

View 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;
}
}

View 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
})

View 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
View 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
}

View File

@@ -0,0 +1,7 @@
export type User = {
id: number
name: string
email: string
}
export const DEPLOY_URL = 'http://localhost:3000'

View File

@@ -0,0 +1,4 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
}

22
tanstack/tsconfig.json Normal file
View 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
}
}