Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ dist/

# turbo
.turbo

# tanstack
.tanstack
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"files.associations": {
"*.css": "tailwindcss"
},
"files.readonlyInclude": {
"**/routeTree.gen.ts": true
},
"files.watcherExclude": {
"**/routeTree.gen.ts": true
},
"prettier.ignorePath": ".gitignore",
"tailwindCSS.classFunctions": ["cva", "cx", "cn"],
"typescript.enablePromptUseWorkspaceTsdk": true,
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

> [!NOTE]
>
> create-t3-turbo now uses better-auth for authentication!
> Look out for bugs as we're working through the last issues,
> especially, the oauth proxy might not play very nice with Expo
> so you might need to disable that in [`@acme/auth`](./packages/auth/src/index.ts)
> create-t3-turbo now includes the option to use Tanstack Start for the web app!

## Installation

Expand Down Expand Up @@ -42,8 +39,13 @@ apps
│ ├─ Navigation using Expo Router
│ ├─ Tailwind CSS v4 using NativeWind v5
│ └─ Typesafe API calls using tRPC
└─ next.js
├─ Next.js 15
├─ nextjs
│ ├─ Next.js 15
│ ├─ React 19
│ ├─ Tailwind CSS v4
│ └─ E2E Typesafe API Server & Client
└─ tanstack-start
├─ Tanstack Start v1 (rc)
├─ React 19
├─ Tailwind CSS v4
└─ E2E Typesafe API Server & Client
Expand Down Expand Up @@ -78,6 +80,10 @@ To get it running, follow the steps below:

### 1. Setup dependencies

> [!NOTE]
>
> While the repo does contain both a Next.js and Tanstack Start version of a web app, you can pick which one you like to use and delete the other folder before starting the setup.

```bash
# Install dependencies
pnpm i
Expand Down
2 changes: 1 addition & 1 deletion apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@acme/prettier-config": "workspace:*",
"@acme/tailwind-config": "workspace:*",
"@acme/tsconfig": "workspace:*",
"@types/node": "^22.18.10",
"@types/node": "catalog:",
"@types/react": "catalog:react19",
"@types/react-dom": "catalog:react19",
"eslint": "catalog:",
Expand Down
4 changes: 2 additions & 2 deletions apps/nextjs/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Toaster } from "@acme/ui/toast";
import { env } from "~/env";
import { TRPCReactProvider } from "~/trpc/react";

import "~/app/globals.css";
import "~/app/styles.css";

export const metadata: Metadata = {
metadataBase: new URL(
Expand Down Expand Up @@ -57,7 +57,7 @@ export default function RootLayout(props: { children: React.ReactNode }) {
geistMono.variable,
)}
>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<ThemeProvider>
<TRPCReactProvider>{props.children}</TRPCReactProvider>
<div className="absolute right-4 bottom-4">
<ThemeToggle />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
@source "../../../../packages/ui/src/*.{ts,tsx}";

@custom-variant dark (&:where(.dark, .dark *));
@custom-variant light (&:where(.light, .light *));
@custom-variant auto (&:where(.auto, .auto *));

@utility container {
margin-inline: auto;
Expand Down
1 change: 1 addition & 0 deletions apps/tanstack-start/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
routeTree.gen.ts
13 changes: 13 additions & 0 deletions apps/tanstack-start/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from "eslint/config";

import { baseConfig, restrictEnvAccess } from "@acme/eslint-config/base";
import { reactConfig } from "@acme/eslint-config/react";

export default defineConfig(
{
ignores: [".nitro/**", ".output/**", ".tanstack/**"],
},
baseConfig,
reactConfig,
restrictEnvAccess,
);
57 changes: 57 additions & 0 deletions apps/tanstack-start/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@acme/tanstack-start",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "pnpm with-env vite dev",
"build": "vite build",
"start": "vite start",
"format": "prettier --check . --ignore-path ../../.gitignore --ignore-path .prettierignore",
"lint": "eslint --flag unstable_native_nodejs_ts_config",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@acme/api": "workspace:*",
"@acme/auth": "workspace:*",
"@acme/db": "workspace:*",
"@acme/ui": "workspace:*",
"@fontsource-variable/geist": "^5.2.8",
"@fontsource-variable/geist-mono": "^5.2.7",
"@t3-oss/env-core": "^0.13.8",
"@tanstack/react-form": "catalog:",
"@tanstack/react-query": "catalog:",
"@tanstack/react-router": "^1.132.47",
"@tanstack/react-router-devtools": "^1.132.51",
"@tanstack/react-router-ssr-query": "^1.132.47",
"@tanstack/react-start": "^1.132.52",
"@trpc/client": "catalog:",
"@trpc/server": "catalog:",
"@trpc/tanstack-react-query": "catalog:",
"better-auth": "catalog:",
"nitro": "npm:nitro-nightly@latest",
"react": "catalog:react19",
"react-dom": "catalog:react19",
"superjson": "2.2.3",
"zod": "catalog:"
},
"devDependencies": {
"@acme/eslint-config": "workspace:*",
"@acme/prettier-config": "workspace:*",
"@acme/tailwind-config": "workspace:*",
"@acme/tsconfig": "workspace:*",
"@tailwindcss/vite": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:react19",
"@types/react-dom": "catalog:react19",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "catalog:",
"prettier": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "^7.1.7",
"vite-tsconfig-paths": "^5.1.4"
},
"prettier": "@acme/prettier-config"
}
Binary file added apps/tanstack-start/public/favicon.ico
Binary file not shown.
3 changes: 3 additions & 0 deletions apps/tanstack-start/src/auth/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createAuthClient } from "better-auth/react";

export const authClient = createAuthClient();
16 changes: 16 additions & 0 deletions apps/tanstack-start/src/auth/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { reactStartCookies } from "better-auth/react-start";

import { initAuth } from "@acme/auth";

import { env } from "~/env";
import { getBaseUrl } from "~/lib/url";

export const auth = initAuth({
baseUrl: getBaseUrl(),
productionUrl: `https://${env.VERCEL_PROJECT_PRODUCTION_URL ?? "turbo.t3.gg"}`,
secret: env.AUTH_SECRET,
discordClientId: env.AUTH_DISCORD_ID,
discordClientSecret: env.AUTH_DISCORD_SECRET,

extraPlugins: [reactStartCookies()],
});
48 changes: 48 additions & 0 deletions apps/tanstack-start/src/component/auth-showcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useNavigate } from "@tanstack/react-router";

import { Button } from "@acme/ui/button";

import { authClient } from "~/auth/client";

export function AuthShowcase() {
const { data: session } = authClient.useSession();
const navigate = useNavigate();

if (!session) {
return (
<Button
size="lg"
onClick={async () => {
const res = await authClient.signIn.social({
provider: "discord",
callbackURL: "/",
});
if (!res.data?.url) {
throw new Error("No URL returned from signInSocial");
}
await navigate({ href: res.data.url, replace: true });
}}
>
Sign in with Discord
</Button>
);
}

return (
<div className="flex flex-col items-center justify-center gap-4">
<p className="text-center text-2xl">
<span>Logged in as {session.user.name}</span>
</p>

<Button
size="lg"
onClick={async () => {
await authClient.signOut();
await navigate({ href: "/", replace: true });
}}
>
Sign out
</Button>
</div>
);
}
36 changes: 36 additions & 0 deletions apps/tanstack-start/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createEnv } from "@t3-oss/env-core";
import { vercel } from "@t3-oss/env-core/presets-zod";
import { z } from "zod/v4";

import { authEnv } from "@acme/auth/env";

export const env = createEnv({
clientPrefix: "VITE_",
extends: [authEnv(), vercel()],
shared: {
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
},
/**
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
server: {
POSTGRES_URL: z.url(),
},

/**
* Specify your client-side environment variables schema here.
* For them to be exposed to the client, prefix them with `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(),
},
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
*/
runtimeEnv: process.env,
skipValidation:
!!process.env.CI || process.env.npm_lifecycle_event === "lint",
});
33 changes: 33 additions & 0 deletions apps/tanstack-start/src/lib/trpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
createTRPCClient,
httpBatchStreamLink,
loggerLink,
} from "@trpc/client";
import { createTRPCContext } from "@trpc/tanstack-react-query";
import SuperJSON from "superjson";

import type { AppRouter } from "@acme/api";

import { env } from "~/env";
import { getBaseUrl } from "~/lib/url";

export const trpcClient = createTRPCClient<AppRouter>({
links: [
loggerLink({
enabled: (op) =>
env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
httpBatchStreamLink({
transformer: SuperJSON,
url: getBaseUrl() + "/api/trpc",
headers() {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
return headers;
},
}),
],
});

export const { useTRPC, TRPCProvider } = createTRPCContext<AppRouter>();
16 changes: 16 additions & 0 deletions apps/tanstack-start/src/lib/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { env } from "~/env";

export function getBaseUrl() {
if (typeof window !== "undefined") {
return window.location.origin;
}
if (env.VERCEL_ENV === "production") {
return `https://${env.VERCEL_PROJECT_PRODUCTION_URL}`;
}
if (env.VERCEL_ENV === "preview") {
return `https://${env.VERCEL_URL}`;
}

// eslint-disable-next-line no-restricted-properties
return `http://localhost:${process.env.PORT ?? 3001}`;
}
Loading