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
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ export default function PageClient() {
</Form>
</div>
</div>
<Separator orientation="vertical" />

<div className="w-1/2 self-stretch py-4 px-4 lg:px-20 bg-zinc-300 dark:bg-zinc-800 hidden md:flex items-center">
<div className="w-full">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: <>
<Typography>
Create a new file called <InlineCode>stack.ts</InlineCode> and add the following code. Here we use react-router-dom as an example.
Create a new file called <InlineCode>stack/client.ts</InlineCode> and add the following code. Here we use react-router-dom as an example.
</Typography>
<CodeBlock
language="tsx"
Expand All @@ -135,7 +135,7 @@ export default function SetupPage(props: { toMetrics: () => void }) {
}
});
`}
title="stack.ts"
title="stack/client.ts"
icon="code"
/>
</>
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion docs/templates/concepts/oauth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
8 changes: 4 additions & 4 deletions docs/templates/customization/custom-pages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
12 changes: 6 additions & 6 deletions docs/templates/getting-started/example-pages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -76,7 +76,7 @@ export const stackClientApp = new StackClientApp({

<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack";
import { stackClientApp } from "./stack/client";

const updateUIState = (user: any | null) => {
const authOptions = document.getElementById("authOptions");
Expand Down Expand Up @@ -148,7 +148,7 @@ export const stackClientApp = new StackClientApp({

<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack";
import { stackClientApp } from "./stack/client";

// Check if user is already signed in
stackClientApp.getUser().then((user) => {
Expand Down Expand Up @@ -253,7 +253,7 @@ export const stackClientApp = new StackClientApp({

<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack";
import { stackClientApp } from "./stack/client";

// Check if user is already signed in
stackClientApp.getUser().then((user) => {
Expand Down Expand Up @@ -335,7 +335,7 @@ export const stackClientApp = new StackClientApp({

<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack";
import { stackClientApp } from "./stack/client";

// Check if user is already signed in
stackClientApp.getUser().then((user) => {
Expand Down Expand Up @@ -413,7 +413,7 @@ export const stackClientApp = new StackClientApp({

<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack";
import { stackClientApp } from "./stack/client";

// Check if user is already signed in
stackClientApp.getUser().then((user) => {
Expand Down
17 changes: 9 additions & 8 deletions docs/templates/getting-started/setup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
</Steps>
</TabsContent>

Expand Down Expand Up @@ -82,11 +83,11 @@ We recommend using our **setup wizard** for a seamless installation experience.
```

<Step>
### Create `stack.ts` file
### Create `stack/server.ts` file
</Step>
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";

Expand Down Expand Up @@ -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:

<Step>
### Create `stack.ts` file
### Create `stack/client.ts` file
</Step>
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";

Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion docs/templates/getting-started/users.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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()`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix import location: use stack/server.ts for server components

Importing stackServerApp from stack/client.ts is incorrect for Server Components.

Apply:

-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()`:
+Since `useUser()` is a stateful hook, you can't use it on server components. Instead, import `stackServerApp` from `stack/server.ts` and call `getUser()`:

Also update the snippet’s import to:

import { stackServerApp } from "@/stack/server";
🤖 Prompt for AI Agents
In docs/templates/getting-started/users.mdx around line 33, the example
incorrectly imports stackServerApp from stack/client.ts for a Server Component;
change the import to use the server-side entry by updating the snippet to import
stackServerApp from "@/stack/server" so the example shows the correct
server-side import for calling getUser() in Server Components.


```tsx title="my-server-component.tsx"
import { stackServerApp } from "@/stack";
Expand Down
4 changes: 3 additions & 1 deletion packages/init-stack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
41 changes: 24 additions & 17 deletions packages/init-stack/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>", "Project ID to use in setup")
.option("--publishable-client-key <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`);
Expand All @@ -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;
Expand Down Expand Up @@ -195,6 +199,7 @@ async function main(): Promise<void> {
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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -567,18 +572,14 @@ const Steps = {
return res;
},

async writeStackAppFile({ type, srcPath, defaultExtension, indentation }: StackAppFileOptions, clientOrServer: string): Promise<StackAppFileResult> {
async writeStackAppFile({ type, srcPath, defaultExtension, indentation }: StackAppFileOptions, clientOrServer: "server" | "client"): Promise<StackAppFileResult> {
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 =
Expand All @@ -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"
Expand All @@ -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 <StackHandler fullPage app={stackServerApp} routeProps={props} />;\n}\n`
);
Expand Down Expand Up @@ -684,7 +691,7 @@ type === "js" && clientOrServer === "server" ? `\n${indentation}secretServerKey:
}
},

async getServerOrClientOrBoth(): Promise<string[]> {
async getServerOrClientOrBoth(): Promise<Array<"server" | "client">> {
if (isClient && isServer) return ["server", "client"];
if (isServer) return ["server"];
if (isClient) return ["client"];
Expand Down Expand Up @@ -742,7 +749,7 @@ async function getUpdatedLayout(originalLayout: string): Promise<LayoutResult |
const importInsertLocationM1 =
firstImportLocationM1 ?? (hasStringAsFirstLine ? layout.indexOf("\n") : -1);
const importInsertLocation = importInsertLocationM1 + 1;
const importStatement = `import { StackProvider, StackTheme } from "@stackframe/stack";\nimport { stackServerApp } from "../stack";\n`;
const importStatement = `import { StackProvider, StackTheme } from "@stackframe/stack";\nimport { stackServerApp } from "../stack/server";\n`;
layout =
layout.slice(0, importInsertLocation) +
importStatement +
Expand Down
Loading