diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx
index 19f2e03ab1..133e0822db 100644
--- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx
+++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx
@@ -114,10 +114,10 @@ export default function SetupPage(props: { toMetrics: () => void }) {
},
{
step: 4,
- title: "Create stack.ts file",
+ title: "Create stack/client.ts file",
content: <>
- Create a new file called stack.ts and add the following code. Here we use react-router-dom as an example.
+ Create a new file called stack/client.ts and add the following code. Here we use react-router-dom as an example.
void }) {
}
});
`}
- title="stack.ts"
+ title="stack/client.ts"
icon="code"
/>
>
@@ -154,7 +154,7 @@ export default function SetupPage(props: { toMetrics: () => void }) {
import { StackHandler, StackProvider, StackTheme } from "@stackframe/react";
import { Suspense } from "react";
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";
- import { stackClientApp } from "./stack";
+ import { stackClientApp } from "./stack/client";
function HandlerRoutes() {
const location = useLocation();
diff --git a/docs/templates/concepts/oauth.mdx b/docs/templates/concepts/oauth.mdx
index 41d068fb43..a6c3536641 100644
--- a/docs/templates/concepts/oauth.mdx
+++ b/docs/templates/concepts/oauth.mdx
@@ -91,7 +91,7 @@ To avoid showing the authorization page twice, you can already request scopes du
To do this, edit the `oauthScopesOnSignIn` setting of your `stackServerApp`:
-```jsx title='stack.ts'
+```jsx title='stack/server.ts'
export const stackServerApp = new StackServerApp({
// ...your other settings...
oauthScopesOnSignIn: {
diff --git a/docs/templates/customization/custom-pages.mdx b/docs/templates/customization/custom-pages.mdx
index 839d0e6fd7..c452aaadfe 100644
--- a/docs/templates/customization/custom-pages.mdx
+++ b/docs/templates/customization/custom-pages.mdx
@@ -23,9 +23,9 @@ export default function CustomSignInPage() {
}
```
-Then you can instruct the Stack app in `stack.ts` to use your custom sign in page:
+Then you can instruct the Stack app in `stack/server.ts` to use your custom sign in page:
-```tsx title="stack.ts"
+```tsx title="stack/server.ts"
export const stackServerApp = new StackServerApp({
// ...
// add these three lines
@@ -67,9 +67,9 @@ export default function CustomOAuthSignIn() {
}
```
-Again, edit the Stack app in `stack.ts` to use your custom sign in page:
+Again, edit the Stack app in `stack/server.ts` to use your custom sign in page:
-```tsx title="stack.ts"
+```tsx title="stack/server.ts"
export const stackServerApp = new StackServerApp({
// ...
// add these three lines
diff --git a/docs/templates/customization/page-examples/forgot-password.mdx b/docs/templates/customization/page-examples/forgot-password.mdx
index a6c942129a..7da437e827 100644
--- a/docs/templates/customization/page-examples/forgot-password.mdx
+++ b/docs/templates/customization/page-examples/forgot-password.mdx
@@ -22,7 +22,7 @@ export default function DefaultForgotPassword() {
To integrate the forgot password page with your application's routing:
1. Create a route for your forgot password page (e.g., `/forgot-password`)
-2. Configure Stack Auth to use your custom route in your `stack.ts` file:
+2. Configure Stack Auth to use your custom route in your `stack/server.ts` file:
```tsx
export const stackServerApp = new StackServerApp({
diff --git a/docs/templates/customization/page-examples/password-reset.mdx b/docs/templates/customization/page-examples/password-reset.mdx
index 51523cc245..e10681337d 100644
--- a/docs/templates/customization/page-examples/password-reset.mdx
+++ b/docs/templates/customization/page-examples/password-reset.mdx
@@ -23,7 +23,7 @@ To integrate the password reset page with your application's routing:
1. Create a route handler that extracts the reset code from the URL (e.g., `/reset-password?code=xyz123`)
2. Pass the code to your password reset component
-3. Configure Stack Auth to use your custom route in your `stack.ts` file:
+3. Configure Stack Auth to use your custom route in your `stack/server.ts` file:
```tsx
export const stackServerApp = new StackServerApp({
diff --git a/docs/templates/getting-started/example-pages.mdx b/docs/templates/getting-started/example-pages.mdx
index e4d3240b5e..913db8b005 100644
--- a/docs/templates/getting-started/example-pages.mdx
+++ b/docs/templates/getting-started/example-pages.mdx
@@ -7,7 +7,7 @@ This guide demonstrates how to integrate Stack Auth with Vite. The same principl
### Initialize the app
-```typescript title="stack.ts"
+```typescript title="stack/client.ts"
import { StackClientApp } from "@stackframe/js";
// Add type declaration for Vite's import.meta.env
@@ -76,7 +76,7 @@ export const stackClientApp = new StackClientApp({
```typescript
- import { stackClientApp } from "./stack";
+ import { stackClientApp } from "./stack/client";
const updateUIState = (user: any | null) => {
const authOptions = document.getElementById("authOptions");
@@ -148,7 +148,7 @@ export const stackClientApp = new StackClientApp({
```typescript
- import { stackClientApp } from "./stack";
+ import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
@@ -253,7 +253,7 @@ export const stackClientApp = new StackClientApp({
```typescript
- import { stackClientApp } from "./stack";
+ import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
@@ -335,7 +335,7 @@ export const stackClientApp = new StackClientApp({
```typescript
- import { stackClientApp } from "./stack";
+ import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
@@ -413,7 +413,7 @@ export const stackClientApp = new StackClientApp({
```typescript
- import { stackClientApp } from "./stack";
+ import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
diff --git a/docs/templates/getting-started/setup.mdx b/docs/templates/getting-started/setup.mdx
index 917fdfb093..a0b3275c7e 100644
--- a/docs/templates/getting-started/setup.mdx
+++ b/docs/templates/getting-started/setup.mdx
@@ -50,7 +50,8 @@ We recommend using our **setup wizard** for a seamless installation experience.
- `app/handler/[...stack]/page.tsx`: This file contains the default pages for sign-in, sign-out, account settings, and more. If you prefer, later you will learn how to [use custom pages](../customization/custom-pages.mdx) instead.
- `app/layout.tsx`: The layout file was updated to wrap the entire body with `StackProvider` and `StackTheme`.
- `app/loading.tsx`: If not yet found, Stack automatically adds a Suspense boundary to your app. This is shown to the user while Stack's async hooks, like `useUser`, are loading.
- - `stack.ts`: This file contains the `stackServerApp` which you can use to access Stack from Server Components, Server Actions, API routes, and middleware.
+ - `stack/server.ts`: This file contains the `stackServerApp` which you can use to access Stack from Server Components, Server Actions, API routes, and middleware.
+ - `stack/client.ts`: This file contains the `stackClientApp` which you can use to access Stack from Client Components
@@ -82,11 +83,11 @@ We recommend using our **setup wizard** for a seamless installation experience.
```
- ### Create `stack.ts` file
+ ### Create `stack/server.ts` file
- Create a new file `stack.ts` in your root directory and fill it with the following:
+ Create a new file `stack/server.ts` in your root directory and fill it with the following:
- ```tsx title="stack.ts"
+ ```tsx title="stack/server.ts"
import "server-only";
import { StackServerApp } from "@stackframe/stack";
@@ -202,11 +203,11 @@ Before getting started, make sure you have a [React project](https://react.dev/l
If you haven't already, [register a new account on Stack](https://app.stack-auth.com/projects), create a project in the dashboard, create a new API key from the left sidebar, and copy the project ID, publishable client key, and secret server key into a new file called `.env.local` in the root of your React project:
- ### Create `stack.ts` file
+ ### Create `stack/client.ts` file
- Create a new file `stack.ts` in your root directory and fill it with the following Stack app initialization code:
+ Create a new file `stack/client.ts` in your root directory and fill it with the following Stack app initialization code:
- ```tsx title="stack.ts"
+ ```tsx title="stack/client.ts"
import { StackClientApp } from "@stackframe/react";
import { useNavigate } from "react-router-dom";
@@ -231,7 +232,7 @@ Before getting started, make sure you have a [React project](https://react.dev/l
import { StackHandler, StackProvider, StackTheme } from "@stackframe/react";
import { Suspense } from "react";
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";
- import { stackClientApp } from "./stack";
+ import { stackClientApp } from "./stack/client";
function HandlerRoutes() {
const location = useLocation();
diff --git a/docs/templates/getting-started/users.mdx b/docs/templates/getting-started/users.mdx
index a60969f789..2e30184c23 100644
--- a/docs/templates/getting-started/users.mdx
+++ b/docs/templates/getting-started/users.mdx
@@ -30,7 +30,7 @@ Sometimes, you want to retrieve the user only if they're signed in, and redirect
## Server Component basics
-Since `useUser()` is a stateful hook, you can't use it on server components. Instead, you can import `stackServerApp` from `stack.ts` and call `getUser()`:
+Since `useUser()` is a stateful hook, you can't use it on server components. Instead, you can import `stackServerApp` from `stack/client.ts` and call `getUser()`:
```tsx title="my-server-component.tsx"
import { stackServerApp } from "@/stack";
diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json
index f228e4bf0e..6115834edd 100644
--- a/packages/init-stack/package.json
+++ b/packages/init-stack/package.json
@@ -24,7 +24,9 @@
"test-run-js:manual": "rimraf test-run-output && npx -y sv create test-run-output --no-install && pnpm run init-stack:local test-run-output",
"test-run-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output --js --client --npm",
"test-run-next:manual": "rimraf test-run-output && npx -y create-next-app@latest test-run-output && pnpm run init-stack:local test-run-output",
- "test-run-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output"
+ "test-run-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output",
+ "test-run-keys-next": "rimraf test-run-output && npx -y create-next-app@latest test-run-output --app --ts --no-src-dir --tailwind --use-npm --eslint --import-alias '##@#/*' --turbopack && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output --project-id my-project-id --publishable-client-key my-publishable-client-key",
+ "test-run-keys-js": "rimraf test-run-output && npx -y sv create test-run-output --template minimal --types ts --no-add-ons --no-install && STACK_DISABLE_INTERACTIVE=true pnpm run init-stack:local test-run-output --js --client --npm --project-id my-project-id --publishable-client-key my-publishable-client-key"
},
"files": [
"README.md",
diff --git a/packages/init-stack/src/index.ts b/packages/init-stack/src/index.ts
index 11059a1780..1c82fd3a0f 100644
--- a/packages/init-stack/src/index.ts
+++ b/packages/init-stack/src/index.ts
@@ -42,6 +42,8 @@ program
.option("--bun", "Use bun as package manager")
.option("--client", "Initialize client-side only")
.option("--server", "Initialize server-side only")
+ .option("--project-id ", "Project ID to use in setup")
+ .option("--publishable-client-key ", "Publishable client key to use in setup")
.option("--no-browser", "Don't open browser for environment variable setup")
.addHelpText('after', `
For more information, please visit https://docs.stack-auth.com/getting-started/setup`);
@@ -58,6 +60,8 @@ const typeFromArgs: string | undefined = options.js ? "js" : options.next ? "nex
const packageManagerFromArgs: string | undefined = options.npm ? "npm" : options.yarn ? "yarn" : options.pnpm ? "pnpm" : options.bun ? "bun" : undefined;
const isClient: boolean = options.client || false;
const isServer: boolean = options.server || false;
+const projectIdFromArgs: string | undefined = options.projectId;
+const publishableClientKeyFromArgs: string | undefined = options.publishableClientKey;
// Commander negates the boolean options with prefix `--no-`
// so `--no-browser` becomes `browser: false`
const noBrowser: boolean = !options.browser;
@@ -195,6 +199,7 @@ async function main(): Promise {
if (type === "next") {
const projectInfo = await Steps.getNextProjectInfo({ packageJson: projectPackageJson });
await Steps.updateNextLayoutFile(projectInfo);
+ await Steps.writeStackAppFile(projectInfo, "client");
await Steps.writeStackAppFile(projectInfo, "server");
await Steps.writeNextHandlerFile(projectInfo);
await Steps.writeNextLoadingFile(projectInfo);
@@ -511,13 +516,13 @@ const Steps = {
"# 1. Go to https://app.stack-auth.com\n" +
"# 2. Create a new project\n" +
"# 3. Copy the keys below\n" +
- "NEXT_PUBLIC_STACK_PROJECT_ID=\n" +
- "NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=\n" +
+ `NEXT_PUBLIC_STACK_PROJECT_ID="${projectIdFromArgs ?? ""}"\n` +
+ `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="${publishableClientKeyFromArgs ?? ""}"\n` +
"STACK_SECRET_SERVER_KEY=\n"
: "# Stack Auth keys\n" +
"# Get these variables by creating a project on https://app.stack-auth.com.\n" +
- "NEXT_PUBLIC_STACK_PROJECT_ID=\n" +
- "NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=\n" +
+ `NEXT_PUBLIC_STACK_PROJECT_ID="${projectIdFromArgs ?? ""}"\n` +
+ `NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY="${publishableClientKeyFromArgs ?? ""}"\n` +
"STACK_SECRET_SERVER_KEY=\n";
laterWriteFile(envLocalPath, envContent);
@@ -567,18 +572,14 @@ const Steps = {
return res;
},
- async writeStackAppFile({ type, srcPath, defaultExtension, indentation }: StackAppFileOptions, clientOrServer: string): Promise {
+ async writeStackAppFile({ type, srcPath, defaultExtension, indentation }: StackAppFileOptions, clientOrServer: "server" | "client"): Promise {
const packageName = await Steps.getStackPackageName(type);
const clientOrServerCap = {
client: "Client",
server: "Server",
- }[clientOrServer] ?? throwErr("unknown clientOrServer " + clientOrServer);
-
- const relativeStackAppPath = {
- js: `stack/${clientOrServer}`,
- next: "stack",
- }[type] ?? throwErr("unknown type");
+ }[clientOrServer as string] ?? throwErr("unknown clientOrServer " + clientOrServer);
+ const relativeStackAppPath = `stack/${clientOrServer}`;
const stackAppPathWithoutExtension = path.join(srcPath, relativeStackAppPath);
const stackAppFileExtension =
@@ -589,24 +590,30 @@ const Steps = {
if (stackAppContent) {
if (!stackAppContent.includes("@stackframe/")) {
throw new UserError(
- `A file at the path ${stackAppPath} already exists. Stack uses the stack.ts file to initialize the Stack SDK. Please remove the existing file and try again.`
+ `A file at the path ${stackAppPath} already exists. Stack uses the stack/${clientOrServer}.ts file to initialize the Stack SDK. Please remove the existing file and try again.`
);
}
throw new UserError(
`It seems that you already installed Stack in this project.`
);
}
+
+ const publishableClientKeyWrite = clientOrServer === "server"
+ ? `process.env.STACK_PUBLISHABLE_CLIENT_KEY ${publishableClientKeyFromArgs ? `|| '${publishableClientKeyFromArgs}'` : ""}`
+ : `'${publishableClientKeyFromArgs ?? 'INSERT_YOUR_PUBLISHABLE_CLIENT_KEY_HERE'}'`;
+
laterWriteFileIfNotExists(
stackAppPath,
`
-${type === "next" ? `import "server-only";` : ""}
+${type === "next" && clientOrServer === "server" ? `import "server-only";` : ""}
import { Stack${clientOrServerCap}App } from ${JSON.stringify(packageName)};
export const stack${clientOrServerCap}App = new Stack${clientOrServerCap}App({
${indentation}tokenStore: ${type === "next" ? '"nextjs-cookie"' : (clientOrServer === "client" ? '"cookie"' : '"memory"')},${
type === "js" ? `\n\n${indentation}// get your Stack Auth API keys from https://app.stack-auth.com${clientOrServer === "client" ? ` and store them in a safe place (eg. environment variables)` : ""}` : ""}${
-type === "js" ? `\n${indentation}publishableClientKey: ${clientOrServer === "server" ? 'process.env.STACK_PUBLISHABLE_CLIENT_KEY' : 'INSERT_YOUR_PUBLISHABLE_CLIENT_KEY_HERE'},` : ""}${
+type === "js" && projectIdFromArgs ? `\n${indentation}projectId: '${projectIdFromArgs}',` : ""}${
+type === "js" ? `\n${indentation}publishableClientKey: ${publishableClientKeyWrite},` : ""}${
type === "js" && clientOrServer === "server" ? `\n${indentation}secretServerKey: process.env.STACK_SECRET_SERVER_KEY,` : ""}
});
`.trim() + "\n"
@@ -630,7 +637,7 @@ type === "js" && clientOrServer === "server" ? `\n${indentation}secretServerKey:
}
laterWriteFileIfNotExists(
handlerPath,
- `import { StackHandler } from "@stackframe/stack";\nimport { stackServerApp } from "../../../stack";\n\nexport default function Handler(props${
+ `import { StackHandler } from "@stackframe/stack";\nimport { stackServerApp } from "../../../stack/server";\n\nexport default function Handler(props${
handlerFileExtension.includes("ts") ? ": unknown" : ""
}) {\n${projectInfo.indentation}return ;\n}\n`
);
@@ -684,7 +691,7 @@ type === "js" && clientOrServer === "server" ? `\n${indentation}secretServerKey:
}
},
- async getServerOrClientOrBoth(): Promise {
+ async getServerOrClientOrBoth(): Promise> {
if (isClient && isServer) return ["server", "client"];
if (isServer) return ["server"];
if (isClient) return ["client"];
@@ -742,7 +749,7 @@ async function getUpdatedLayout(originalLayout: string): Promise