Getting Started with TanStack Start And Convex: Your SaaS Foundation
Learn how to get started with TanStack Start And Convex and start building your first SaaS

TanStack Start
Part 1 of 1
Welcome to the first article in our series on building a modern SaaS application! We’ll use TanStack Start, a cutting-edge React framework, alongside Convex, Clerk, Radix UI, and Polar.sh to create a full-stack app.
Important Note: TanStack Start is in Alpha/Beta. APIs may change before a stable release. Embrace the innovation, but expect potential updates!
What is TanStack Start?
TanStack Start is a modern React framework from the TanStack team, known for libraries like TanStack Query and Router. It offers:
- File-based Routing: Similar to Next.js or Remix.
- Server-Side Rendering (SSR): With client-side hydration.
- Integrated Data Fetching: Seamless with TanStack Query.
- Vite Tooling: Fast development and builds.
What is Convex?
Convex is a backend-as-a-service platform that simplifies building scalable, real-time applications. It provides:
- Realtime Database: Automatically syncs data changes to clients, ideal for dynamic apps. Learn more in the Convex Database Docs.
- Serverless Functions: Write queries (for reading data) and mutations (for writing data) in TypeScript/JavaScript, hosted by Convex.
- Type Safety: Shares types between frontend and backend, reducing errors.
- Dashboard: A web-based Convex Dashboard to manage data, view logs, and debug.
- Additional Features: File storage, scheduled tasks, and vector search.
Convex integrates seamlessly with TanStack Start via the @convex-dev/react-query
package, allowing you to fetch and mutate data using TanStack Query hooks.
Installation (The Easy Way with Convex)
Use the Convex template to scaffold a project with TanStack Start and Convex pre-configured:
npm create convex@latest -- -t tanstack-start my-saas-app
cd my-saas-app
This creates a project in my-saas-app
with React, TypeScript, TanStack Start, and Convex client setup. The template includes a convex/
directory for backend logic and pre-configures environment variables.
For manual setup, refer to the TanStack Start Getting Started Guide and integrate Convex in the next article.
Starting the Development Environment
Run the development server to initialize your app and Convex backend:
npm run dev
You’ll see output like this:
> [email protected] dev
> npx convex dev --once && npm-run-all --parallel dev:convex dev:start
? Welcome to Convex! Would you like to login to your account? Start without an account (run Convex locally)
Let's set up your first project.
? Choose a name: my-saas-app
This command, `npx convex dev`, will run your Convex backend locally and update it with the function you write in the `convex/` directory.
Use `npx convex dashboard` to view and interact with your project from a web UI.
Use `npx convex docs` to read the docs and `npx convex help` to see other commands.
? Continue? Yes
✔ Downloaded Convex backend binary
✔ Downloaded Convex dashboard
✔ Started running a deployment locally at http://127.0.0.1:3210 and saved its:
name as CONVEX_DEPLOYMENT to .env.local
URL as VITE_CONVEX_URL to .env.local
Run `npx convex login` at any time to create an account and link this deployment.
Write your Convex functions in convex/
Give us feedback at https://convex.dev/community or [email protected]
View the Convex dashboard at http://127.0.0.1:6790/?d=anonymous-my-saas-app
✔ 11:29:40 Convex functions ready! (959.55ms)
> [email protected] dev:start
> vinxi dev
> [email protected] dev:convex
> convex dev
vinxi v0.4.3
vinxi starting dev server
♻️ Generating routes...
✔ Started running a deployment locally at http://127.0.0.1:3210 and saved its name as CONVEX_DEPLOYMENT to .env.local
Run `npx convex login` at any time to create an account and link this deployment.
Write your Convex functions in convex/
Give us feedback at https://convex.dev/community or [email protected]
View the Convex dashboard at http://127.0.0.1:6790/?d=anonymous-my-saas-app
⠋ Preparing Convex functions...
⠙ Preparing Convex functions...
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
✔ 11:29:42 Convex functions ready! (839.54ms)
Warning: A notFoundError was encountered on the route with ID "__root__", but a notFoundComponent option was not configured...
What’s Happening?
- Convex Initialization:
npx convex dev --once
sets up a local Convex backend, prompting you to name your project (e.g.,my-saas-app
). It runs athttp://127.0.0.1:3210
and savesVITE_CONVEX_URL
andCONVEX_DEPLOYMENT
to.env.local
. These variables connect your frontend to the Convex backend. - Parallel Execution:
npm-run-all --parallel dev:convex dev:start
runs the Convex dev server (npx convex dev
) and Vite dev server (vinxi dev
) concurrently. - Vite Server: Your app is available at
http://localhost:3000
(port may vary). Open this URL to see your app. - Convex Dashboard: Visit
http://127.0.0.1:6790/?d=anonymous-my-saas-app
to manage your backend data, view logs, and test functions. Learn more about the dashboard in the Convex Dashboard Docs. - Not Found Warning: The
notFoundError
indicates TanStack Router needs a custom 404 component, which we’ll address below.
Tip: Run npx convex dashboard
to open the dashboard directly, or npx convex login
to link your local deployment to a Convex account for cloud syncing.
Understanding the Project Structure
Key files and directories:
.
├── app/
│ ├── routes/ # Page routes
│ │ ├── __root.tsx # Root layout (HTML structure, global providers)
│ │ └── index.tsx # Homepage component ('/')
│ ├── client.tsx # Client-side entry (hydrates SSR)
│ ├── router.tsx # Router setup (integrates Convex/Clerk)
│ ├── routeTree.gen.ts # Auto-generated route tree
│ └── ssr.tsx # Server-side rendering entry
├── convex/ # Backend logic (Convex functions, schema)
│ ├── schema.ts # Database schema (defines tables)
│ └── ... # Backend functions (queries, mutations)
├── public/ # Static assets
├── .env.local # Environment variables (e.g., VITE_CONVEX_URL)
├── app.config.ts # TanStack Start config
├── convex.config.ts # Convex config
├── package.json
├── tsconfig.json # TypeScript config
└── vite.config.ts # Vite config
- app/routes/__root.tsx: Defines the app’s layout, including
<html>
,<head>
,<body>
, and common UI like headers.<Outlet />
renders child routes. - app/routes/index.tsx: Renders the
/
route (homepage). - convex/: Contains backend logic.
schema.ts
defines your database structure (see Convex Schema Docs). Other files will hold queries and mutations. - .env.local: Stores sensitive keys like
VITE_CONVEX_URL
. Never commit this file to Git.
Basic Routing
TanStack Start uses file-based routing via TanStack Router:
- app/routes/index.tsx: Uses
createFileRoute('/')
to define the homepage. - app/routes/__root.tsx: Uses
createRootRouteWithContext
to set up the root layout and context (e.g.,queryClient
for TanStack Query,convexClient
for Convex).
Adding a Header and Footer
Let’s add a header and footer to app/routes/__root.tsx
to provide consistent navigation and branding across all pages:
// app/routes/__root.tsx
import { QueryClient } from "@tanstack/react-query";
import { createRootRouteWithContext, Link } from "@tanstack/react-router";
import { Outlet, ScrollRestoration } from "@tanstack/react-router";
import { Meta, Scripts } from "@tanstack/start";
import * as React from "react";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { ConvexReactClient } from "convex/react";
interface MyRouterContext {
queryClient: QueryClient;
convexClient: ConvexReactClient;
convexQueryClient: ConvexQueryClient;
}
export const Route = createRootRouteWithContext<MyRouterContext>()({
head: () => ({
meta: [
{ charSet: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{ title: "My SaaS App" },
],
}),
notFoundComponent: () => (
<div style={{ textAlign: 'center', padding: '2rem' }}>
<h1>404 - Page Not Found</h1>
<Link to="/">Go Home</Link>
</div>
),
component: RootComponent,
});
function RootComponent() {
return (
<RootDocument>
<header
style={{
padding: '1rem',
borderBottom: '1px solid #eee',
background: '#f8f9fa',
position: 'sticky',
top: 0,
zIndex: 10,
}}
role="banner"
>
<nav aria-label="Main navigation">
<Link
to="/"
style={{
marginRight: '1rem',
fontWeight: 'bold',
color: '#333',
textDecoration: 'none',
}}
aria-label="Home page"
>
My SaaS App
</Link>
</nav>
</header>
<main style={{ padding: '1rem', minHeight: '80vh' }} role="main">
<Outlet />
</main>
<footer
style={{
padding: '1rem',
borderTop: '1px solid #eee',
textAlign: 'center',
background: '#f8f9fa',
}}
role="contentinfo"
>
<p>© {new Date().getFullYear()} My SaaS App. All rights reserved.</p>
</footer>
</RootDocument>
);
}
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Meta />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
Detailed Explanation of Header and Footer Code:
-
RootComponent: This function defines the core layout of your application, rendered for every route. It wraps the
<header>
,<main>
, and<footer>
in aRootDocument
component, which provides the HTML structure (<html>
,<head>
,<body>
).- Purpose: Ensures consistent UI elements (like navigation and branding) appear on all pages.
- Structure: Uses semantic HTML elements (
<header>
,<main>
,<footer>
) for accessibility and SEO.
-
Header:
- Element:
<header role="banner">
marks the header as the primary banner of the page, improving accessibility for screen readers. - Styling:
padding: '1rem'
: Adds spacing for a clean look.borderBottom: '1px solid #eee'
: Adds a subtle divider.background: '#f8f9fa'
: Uses a light gray background for visual distinction.position: 'sticky', top: 0, zIndex: 10
: Keeps the header fixed at the top during scrolling, ensuring navigation is always accessible.
- Navigation:
<nav aria-label="Main navigation">
: Labels the navigation for accessibility.<Link to="/" ...>My SaaS App</Link>
: Uses TanStack Router’sLink
component for client-side navigation to the homepage (/
).- Styling:
fontWeight: 'bold', color: '#333', textDecoration: 'none'
makes the link prominent and removes the default underline. - Accessibility:
aria-label="Home page"
provides context for screen readers.
- Styling:
- Purpose: The header provides a consistent navigation bar, starting with a single link to the homepage. Later articles will add more links (e.g., to chat or pricing pages).
- Element:
-
Main:
- Element:
<main role="main">
designates the primary content area, aiding accessibility. - Styling:
padding: '1rem', minHeight: '80vh'
ensures content is spaced and the main area takes up most of the viewport height, preventing the footer from appearing too high on short pages. - Outlet:
<Outlet />
is a TanStack Router component that renders the content of the current route (e.g.,index.tsx
for/
).
- Element:
-
Footer:
- Element:
<footer role="contentinfo">
marks the footer as supplementary information, enhancing accessibility. - Styling:
padding: '1rem'
: Adds spacing.borderTop: '1px solid #eee'
: Adds a divider.textAlign: 'center'
: Centers the text.background: '#f8f9fa'
: Matches the header’s background for consistency.
- Content: Displays a dynamic copyright notice using the current year (
new Date().getFullYear()
). - Purpose: Provides a professional touch and space for additional links or information in the future.
- Element:
-
RootDocument:
- Purpose: Defines the HTML structure required for SSR and client-side rendering.
- Components:
<Meta />
: Injects metadata (e.g.,<title>
,<meta>
tags) defined in thehead
function.<ScrollRestoration />
: Ensures scroll position is preserved during navigation.<Scripts />
: Includes JavaScript bundles for client-side interactivity.
- Accessibility: Sets
lang="en"
on<html>
for language clarity.
-
NotFoundComponent:
- Addresses the
notFoundError
warning by providing a custom 404 page. - Includes a simple message and a link back to the homepage.
- Addresses the
-
Accessibility Considerations:
- Semantic elements (
header
,main
,footer
) and ARIA roles (banner
,main
,contentinfo
) improve screen reader compatibility. aria-label
on navigation elements enhances usability for assistive technologies.- Styling ensures sufficient contrast (e.g.,
#333
text on#f8f9fa
background).
- Semantic elements (
-
Extensibility: The header’s
<nav>
can later include additional<Link>
components for routes like/chat
or/pricing
, as shown in later articles.
Troubleshooting Common Issues
- Port Conflicts: If
localhost:3000
is taken, Vite uses another port (check terminal output). - Convex Setup Fails: Ensure internet connectivity. Run
npx convex dev --once
manually if needed. Check the Convex Troubleshooting Docs. - Not Found Warning: The
notFoundComponent
above resolves this. Alternatively, setdefaultNotFoundComponent
inapp/router.tsx
. - Environment Variables: Verify
.env.local
containsVITE_CONVEX_URL
andCONVEX_DEPLOYMENT
. See Convex Environment Variables. - Convex Dashboard Access: If the dashboard URL fails, run
npx convex dashboard
or check firewall settings.
Next, we’ll integrate Convex for the backend and Clerk for authentication, building on the foundation laid here!
Related Posts

Stop AI Crawler Bots: How to Safeguard Your Website
Let's see the various alternatives that exist to block all AI Crawler bots that try to access your website content.

How To Add Accordion FAQs Drop-Down to Carrd.co
Let's see how an accordion can be added easily and free to a carrd.co website.

How To Add a Sticky Header to Carrd
Let's see how you can add a sticky header to a Carrd website for a better experience.