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
Join BitBuddies
Level up your DevOps skills with hands-on courses on CloudPanel and Dockploy. Join our community of developers and get expert workshops to accelerate your online journey.
Start your journey to DevOps mastery today! π
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:
> my-saas-app@1.0.0 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 support@convex.dev
View the Convex dashboard at http://127.0.0.1:6790/?d=anonymous-my-saas-app
β 11:29:40 Convex functions ready! (959.55ms)
> my-saas-app@1.0.0 dev:start
> vinxi dev
> my-saas-app@1.0.0 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 support@convex.dev
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 --oncesets up a local Convex backend, prompting you to name your project (e.g.,my-saas-app). It runs athttp://127.0.0.1:3210and savesVITE_CONVEX_URLandCONVEX_DEPLOYMENTto.env.local. These variables connect your frontend to the Convex backend. - Parallel Execution:
npm-run-all --parallel dev:convex dev:startruns 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-appto manage your backend data, view logs, and test functions. Learn more about the dashboard in the Convex Dashboard Docs. - Not Found Warning: The
notFoundErrorindicates 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.tsdefines 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
createRootRouteWithContextto set up the root layout and context (e.g.,queryClientfor TanStack Query,convexClientfor 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 aRootDocumentcomponent, 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βsLinkcomponent 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.tsxfor/).
- 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 theheadfunction.<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
notFoundErrorwarning 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-labelon navigation elements enhances usability for assistive technologies.- Styling ensures sufficient contrast (e.g.,
#333text on#f8f9fabackground).
- Semantic elements (
-
Extensibility: The headerβs
<nav>can later include additional<Link>components for routes like/chator/pricing, as shown in later articles.
Troubleshooting Common Issues
- Port Conflicts: If
localhost:3000is taken, Vite uses another port (check terminal output). - Convex Setup Fails: Ensure internet connectivity. Run
npx convex dev --oncemanually if needed. Check the Convex Troubleshooting Docs. - Not Found Warning: The
notFoundComponentabove resolves this. Alternatively, setdefaultNotFoundComponentinapp/router.tsx. - Environment Variables: Verify
.env.localcontainsVITE_CONVEX_URLandCONVEX_DEPLOYMENT. See Convex Environment Variables. - Convex Dashboard Access: If the dashboard URL fails, run
npx convex dashboardor check firewall settings.
Next, weβll integrate Convex for the backend and Clerk for authentication, building on the foundation laid here!
Related Posts
How to Create A Carrd.co Mobile Responsive Navbar
Learn how you can add a Responsive Navbar to your carrd website that has a mobile toggle.
How To Add Custom Domain to Carrd.co Website
Let's see how a custom domain name can be added to your carrd.co website.
Add Localization(i18n) to Your Astro Project (Complete Guide)
Building multilingual websites is essential in today's global digital landscape. While Astro doesn't provide built-in URL localization out of the box, this comprehensive guide will show you how to implement a complete internationalization (i18n) system with SEO-friendly URLs, dynamic routing, and seamless language switching.