Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
ErrorComponent,
Link,
rootRouteId,
useMatch,
useRouter,
} from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router'

/**
* Render a default error UI for a caught route error with controls to retry or navigate.
*
* Logs the provided error to the console and renders an ErrorComponent alongside buttons
* to invalidate the router (retry) and either navigate home or go back in history depending
* on whether the current match is the root route.
*
* @param error - The caught error provided to the catch boundary
* @returns A React element displaying the error and action controls to retry or navigate
*/
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-sm text-white uppercase font-extrabold`}
>
Try Again
</button>
{isRoot ? (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
>
Home
</Link>
) : (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
onClick={(e) => {
e.preventDefault()
window.history.back()
}}
>
Go Back
</Link>
)}
</div>
</div>
)
}
31 changes: 31 additions & 0 deletions examples/react/start-basic-netlify/src/components/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Link } from '@tanstack/react-router'

/**
* Render a simple "not found" view that shows an optional message and two navigation actions.
*
* @param children - Optional custom content to display instead of the default "The page you are looking for does not exist." message.
* @returns A React element containing the message area and two controls: a "Go back" button (calls history.back) and a "Start Over" link to the root path.
*/
export function NotFound({ children }: { children?: React.ReactNode }) {
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-sm uppercase font-black text-sm"
>
Go back
</button>
<Link
to="/"
className="bg-cyan-600 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
>
Start Over
</Link>
</p>
</div>
)
}
12 changes: 12 additions & 0 deletions examples/react/start-basic-netlify/src/components/PostError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ErrorComponent } from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router'

/**
* Displays the given routing error.
*
* @param error - The error object provided by the router for the failed route.
* @returns A JSX element that renders the error UI.
*/
export function PostErrorComponent({ error }: ErrorComponentProps) {
return <ErrorComponent error={error} />
}
12 changes: 12 additions & 0 deletions examples/react/start-basic-netlify/src/components/UserError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ErrorComponent } from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router'

/**
* Renders an error UI for a routing error.
*
* @param error - The error object produced during route handling to display to the user.
* @returns The rendered error UI element.
*/
export function UserErrorComponent({ error }: ErrorComponentProps) {
return <ErrorComponent error={error} />
}
20 changes: 20 additions & 0 deletions examples/react/start-basic-netlify/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
import { DefaultCatchBoundary } from './components/DefaultCatchBoundary'
import { NotFound } from './components/NotFound'

/**
* Create and return a configured TanStack React Router instance for the application.
*
* @returns A router configured with the application's `routeTree`, intent preloading, `DefaultCatchBoundary` as the default error component, `NotFound` as the default not-found component, and scroll restoration enabled.
*/
export function getRouter() {
const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => <NotFound />,
scrollRestoration: true,
})
return router
}
140 changes: 140 additions & 0 deletions examples/react/start-basic-netlify/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/// <reference types="vite/client" />
import {
HeadContent,
Link,
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' },
],
scripts: [
{
src: '/customScript.js',
type: 'text/javascript',
},
],
}),
errorComponent: DefaultCatchBoundary,
notFoundComponent: () => <NotFound />,
shellComponent: RootDocument,
})

/**
* Render the application's HTML document shell with global navigation and injected head/scripts.
*
* Renders a complete HTML structure including HeadContent, a top navigation bar of internal links,
* a content outlet for `children`, the TanStack Router Devtools, and Scripts.
*
* @param children - Routed content to render inside the document body
* @returns The root HTML document element that wraps routed content and provides head, navigation, devtools, and scripts
*/
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>
)
}
21 changes: 21 additions & 0 deletions examples/react/start-basic-netlify/src/routes/_pathlessLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Outlet, createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_pathlessLayout')({
component: LayoutComponent,
})

/**
* Layout component that renders a header and an Outlet for nested routes.
*
* @returns A JSX element containing a container with a top header and an <Outlet /> for nested route content.
*/
function LayoutComponent() {
return (
<div className="p-2">
<div className="border-b">I'm a layout</div>
<div>
<Outlet />
</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Link, Outlet, createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_pathlessLayout/_nested-layout')({
component: LayoutComponent,
})

/**
* Renders a nested layout with a header, two navigation links (Route A and Route B), and an Outlet for nested route content.
*
* @returns The JSX element containing the layout header, navigation links with active styling, and an Outlet for child routes.
*/
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>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-a')(
{
component: LayoutAComponent,
},
)

/**
* Renders the layout A component showing "I'm A!".
*
* @returns A JSX element containing a div with the text "I'm A!".
*/
function LayoutAComponent() {
return <div>I'm A!</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/_pathlessLayout/_nested-layout/route-b')(
{
component: LayoutBComponent,
},
)

/**
* Renders a simple layout component that displays "I'm B!".
*
* @returns A JSX element containing a `div` with the text "I'm B!".
*/
function LayoutBComponent() {
return <div>I'm B!</div>
}
Loading
Loading