diff --git a/apps/backend/src/app/global-error.tsx b/apps/backend/src/app/global-error.tsx
index 55bb51bb7..d65c7f4d1 100644
--- a/apps/backend/src/app/global-error.tsx
+++ b/apps/backend/src/app/global-error.tsx
@@ -12,7 +12,7 @@ export default function GlobalError({ error }: any) {
return (
-
+
[An unhandled error occurred.]
-
+
+
{children}
diff --git a/apps/dashboard/src/app/global-error.tsx b/apps/dashboard/src/app/global-error.tsx
index 60b99ce82..e43375bf6 100644
--- a/apps/dashboard/src/app/global-error.tsx
+++ b/apps/dashboard/src/app/global-error.tsx
@@ -26,7 +26,7 @@ export default function GlobalError({ error }: any) {
return (
-
+
{isProdLike ? (
) : (
diff --git a/apps/dashboard/src/app/layout.tsx b/apps/dashboard/src/app/layout.tsx
index 62beeec09..5d2d64b4f 100644
--- a/apps/dashboard/src/app/layout.tsx
+++ b/apps/dashboard/src/app/layout.tsx
@@ -67,7 +67,7 @@ export default function RootLayout({
}
return (
-
+
@@ -85,10 +85,9 @@ export default function RootLayout({
diff --git a/apps/e2e/tests/general/setup-wizard.ts b/apps/e2e/tests/general/setup-wizard.ts
new file mode 100644
index 000000000..4f57b5d9a
--- /dev/null
+++ b/apps/e2e/tests/general/setup-wizard.ts
@@ -0,0 +1,14 @@
+import { exec } from "child_process";
+import { describe } from "vitest";
+import { it } from "../helpers";
+
+describe("Setup wizard", () => {
+ it("completes successfully with create-next-app", async ({ expect }) => {
+ const [error, stdout, stderr] = await new Promise<[Error | null, string, string]>((resolve) => {
+ exec("pnpm -C packages/init-stack run test-run-auto", (error, stdout, stderr) => {
+ resolve([error, stdout, stderr]);
+ });
+ });
+ expect(error, `Expected no error to be thrown!\n\n\n\nstdout: ${stdout}\n\n\n\nstderr: ${stderr}`).toBeNull();
+ }, 120_000);
+});
diff --git a/examples/demo/src/app/global.css b/examples/demo/src/app/global.css
index 7a6c5b0a8..b8ee13918 100644
--- a/examples/demo/src/app/global.css
+++ b/examples/demo/src/app/global.css
@@ -8,7 +8,7 @@
--foreground: black;
}
-[data-stack-theme='dark'] {
+html:has(head > [data-stack-theme="dark"]) {
--background: black;
--foreground: white;
}
@@ -16,4 +16,4 @@
body {
background-color: var(--background);
color: var(--foreground);
-}
\ No newline at end of file
+}
diff --git a/examples/demo/src/app/layout.tsx b/examples/demo/src/app/layout.tsx
index abd3607f9..4a9d6a036 100644
--- a/examples/demo/src/app/layout.tsx
+++ b/examples/demo/src/app/layout.tsx
@@ -19,7 +19,7 @@ export default function RootLayout({
return (
-
+
diff --git a/examples/demo/tailwind.config.js b/examples/demo/tailwind.config.js
index a2e4a0264..1141cd681 100644
--- a/examples/demo/tailwind.config.js
+++ b/examples/demo/tailwind.config.js
@@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
- darkMode: ["selector", '[data-stack-theme="dark"]'],
+ darkMode: ["selector", 'html:has(head > [data-stack-theme="dark"])'],
content: [
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@stackframe/stack-ui/src/**/*.{ts,tsx}",
diff --git a/examples/docs-examples/src/app/global.css b/examples/docs-examples/src/app/global.css
index ffa369697..fc0fd0078 100644
--- a/examples/docs-examples/src/app/global.css
+++ b/examples/docs-examples/src/app/global.css
@@ -8,7 +8,7 @@
--foreground: black;
}
-[data-stack-theme='dark'] {
+html:has(head > [data-stack-theme="dark"]) {
--background: black;
--foreground: white;
}
@@ -16,4 +16,4 @@
body {
background-color: var(--background);
color: var(--foreground);
-} */
\ No newline at end of file
+} */
diff --git a/examples/docs-examples/src/app/layout.tsx b/examples/docs-examples/src/app/layout.tsx
index 472bc8add..66c43a4c3 100644
--- a/examples/docs-examples/src/app/layout.tsx
+++ b/examples/docs-examples/src/app/layout.tsx
@@ -20,7 +20,7 @@ export default function RootLayout({
return (
-
+
diff --git a/examples/supabase/app/layout.tsx b/examples/supabase/app/layout.tsx
index c70f9deb0..92e05551f 100644
--- a/examples/supabase/app/layout.tsx
+++ b/examples/supabase/app/layout.tsx
@@ -3,7 +3,7 @@ import { stackServerApp } from "../stack";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
-
+
diff --git a/packages/init-stack/.gitignore b/packages/init-stack/.gitignore
new file mode 100644
index 000000000..a488bad01
--- /dev/null
+++ b/packages/init-stack/.gitignore
@@ -0,0 +1 @@
+test-run-output
diff --git a/packages/init-stack/index.mjs b/packages/init-stack/index.mjs
index e5bac5968..cf4dbbdde 100644
--- a/packages/init-stack/index.mjs
+++ b/packages/init-stack/index.mjs
@@ -75,7 +75,7 @@ async function main() {
);
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
- const nextVersionInPackageJson = packageJson?.dependencies["next"] ?? packageJson?.devDependencies["next"];
+ const nextVersionInPackageJson = packageJson?.dependencies?.["next"] ?? packageJson?.devDependencies?.["next"];
if (!nextVersionInPackageJson) {
throw new UserError(
`The project at ${projectPath} does not appear to be a Next.js project, or does not have 'next' installed as a dependency. Only Next.js projects are currently supported.`
@@ -195,6 +195,8 @@ async function main() {
);
}
+ const stackPackageName = process.env.STACK_PACKAGE_NAME_OVERRIDE || "@stackframe/stack";
+
const isReady = await inquirer.prompt([
{
type: "confirm",
@@ -209,7 +211,7 @@ async function main() {
console.log();
console.log(`${ansis.bold}Installing dependencies...${ansis.clear}`);
- await shellNicelyFormatted(`${installCommand} @stackframe/stack`, {
+ await shellNicelyFormatted(`${installCommand} ${stackPackageName}`, {
shell: true,
cwd: projectPath,
});
@@ -294,7 +296,9 @@ main()
"For more information, please visit https://docs.stack-auth.com/getting-started/setup"
);
console.log();
- await open("https://app.stack-auth.com/wizard-congrats");
+ if (!process.env.STACK_DISABLE_INTERACTIVE) {
+ await open("https://app.stack-auth.com/wizard-congrats");
+ }
})
.catch((err) => {
if (!(err instanceof UserError)) {
diff --git a/packages/init-stack/package.json b/packages/init-stack/package.json
index afd66ab31..9cd3b8291 100644
--- a/packages/init-stack/package.json
+++ b/packages/init-stack/package.json
@@ -5,7 +5,11 @@
"main": "index.mjs",
"bin": "./index.mjs",
"scripts": {
- "init-stack": "node index.mjs"
+ "clean": "rimraf test-run-output && rimraf node_modules",
+ "init-stack": "node index.mjs",
+ "init-stack:local": "STACK_PACKAGE_NAME_OVERRIDE=../../stack node index.mjs",
+ "test-run": "rimraf test-run-output && npx -y create-next-app@latest test-run-output && pnpm run init-stack:local test-run-output",
+ "test-run-auto": "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"
},
"files": [
"README.md",
diff --git a/packages/stack-emails/package.json b/packages/stack-emails/package.json
index f717c3ff6..ba559d133 100644
--- a/packages/stack-emails/package.json
+++ b/packages/stack-emails/package.json
@@ -28,12 +28,12 @@
}
},
"peerDependencies": {
- "react": ">=18.2",
- "react-dom": ">=18.2",
+ "react": ">=18.2 || >=19.0.0-rc.0",
+ "react-dom": ">=18.2 || >=19.0.0-rc.0",
"yup": "^1.4.0",
- "@types/react": ">=18.2.66",
- "@types/react-dom": ">=18.2.66",
- "next": ">=14.1.0"
+ "@types/react": ">=18.2.66 || >=19.0.0-rc.0",
+ "@types/react-dom": ">=18.2.66 || >=19.0.0-rc.0",
+ "next": ">=14.1.0 || >=15.0.0-rc.0"
},
"peerDependenciesMeta": {
"@types/react": {
diff --git a/packages/stack-sc/package.json b/packages/stack-sc/package.json
index c5de56bba..8e6011c02 100644
--- a/packages/stack-sc/package.json
+++ b/packages/stack-sc/package.json
@@ -30,11 +30,11 @@
"LICENSE"
],
"peerDependencies": {
- "next": ">=14.1",
- "react": ">=18.2",
- "react-dom": ">=18.2",
- "@types/react": ">=18.3.12",
- "@types/react-dom": ">=18.3.1"
+ "next": ">=14.1 || >=15.0.0-rc.0",
+ "react": ">=18.2 || >=19.0.0-rc.0",
+ "react-dom": ">=18.2 || >=19.0.0-rc.0",
+ "@types/react": ">=18.3.12 || >=19.0.0-rc.0",
+ "@types/react-dom": ">=18.3.1 || >=19.0.0-rc.0"
},
"peerDependenciesMeta": {
"@types/react": {
diff --git a/packages/stack-shared/package.json b/packages/stack-shared/package.json
index cbc013f09..961714461 100644
--- a/packages/stack-shared/package.json
+++ b/packages/stack-shared/package.json
@@ -27,11 +27,11 @@
}
},
"peerDependencies": {
- "react": ">=18.2",
- "react-dom": ">=18.2",
- "@types/react": ">=18.2",
- "@types/react-dom": ">=18.2",
- "next": ">=14.1.0",
+ "react": ">=18.2 || >=19.0.0-rc.0",
+ "react-dom": ">=18.2 || >=19.0.0-rc.0",
+ "@types/react": ">=18.2 || >=19.0.0-rc.0",
+ "@types/react-dom": ">=18.2 || >=19.0.0-rc.0",
+ "next": ">=14.1.0 || >=15.0.0-rc.0",
"yup": "^1.4.0"
},
"peerDependenciesMeta": {
diff --git a/packages/stack-shared/src/hooks/use-hash.tsx b/packages/stack-shared/src/hooks/use-hash.tsx
index e5868ccb8..fc82b6851 100644
--- a/packages/stack-shared/src/hooks/use-hash.tsx
+++ b/packages/stack-shared/src/hooks/use-hash.tsx
@@ -1,10 +1,14 @@
-import { useParams } from "next/navigation";
-import { useEffect, useState } from "react";
+import { useSyncExternalStore } from "react";
+import { suspendIfSsr } from "../utils/react";
-const getHash = () => typeof window === "undefined" ? undefined : window.location.hash.substring(1);
export const useHash = () => {
- const params = useParams();
- const [hash, setHash] = useState(getHash());
- useEffect(() => setHash(getHash()), [params]);
- return hash;
+ suspendIfSsr("useHash");
+ return useSyncExternalStore(
+ (onChange) => {
+ const interval = setInterval(() => onChange(), 10);
+ return () => clearInterval(interval);
+ },
+ () => window.location.hash.substring(1)
+ );
};
+
diff --git a/packages/stack-ui/package.json b/packages/stack-ui/package.json
index 68701a663..59d697b49 100644
--- a/packages/stack-ui/package.json
+++ b/packages/stack-ui/package.json
@@ -23,11 +23,11 @@
}
},
"peerDependencies": {
- "react": ">=18.2",
- "react-dom": ">=18.2",
- "@types/react": ">=18.2.12",
- "@types/react-dom": ">=18.2.12",
- "next": ">=14.1.0",
+ "react": ">=18.2 || >=19.0.0-rc.0",
+ "react-dom": ">=18.2 || >=19.0.0-rc.0",
+ "@types/react": ">=18.2.12 || >=19.0.0-rc.0",
+ "@types/react-dom": ">=18.2.12 || >=19.0.0-rc.0",
+ "next": ">=14.1.0 || >=15.0.0-rc.0",
"yup": "^1.4.0"
},
"peerDependenciesMeta": {
diff --git a/packages/stack/package.json b/packages/stack/package.json
index 7ebc5a74a..12ab819b8 100644
--- a/packages/stack/package.json
+++ b/packages/stack/package.json
@@ -66,11 +66,11 @@
"yup": "^1.4.0"
},
"peerDependencies": {
- "next": ">=14.1",
- "react": ">=18.2",
- "react-dom": ">=18.2",
- "@types/react": ">=18.2",
- "@types/react-dom": ">=18.2"
+ "next": ">=14.1 || >=15.0.0-rc.0",
+ "react": ">=18.2 || >=19.0.0-rc.0",
+ "react-dom": ">=18.2 || >=19.0.0-rc.0",
+ "@types/react": ">=18.2 || >=19.0.0-rc.0",
+ "@types/react-dom": ">=18.2 || >=19.0.0-rc.0"
},
"peerDependenciesMeta": {
"@types/react": {
diff --git a/packages/stack/src/components/elements/maybe-full-page.tsx b/packages/stack/src/components/elements/maybe-full-page.tsx
index 2999931ee..b4daf3be9 100644
--- a/packages/stack/src/components/elements/maybe-full-page.tsx
+++ b/packages/stack/src/components/elements/maybe-full-page.tsx
@@ -30,7 +30,6 @@ export function MaybeFullPage({
<>
diff --git a/packages/stack/src/providers/theme-provider.tsx b/packages/stack/src/providers/theme-provider.tsx
index 9b0c2e1c2..a9145e24d 100644
--- a/packages/stack/src/providers/theme-provider.tsx
+++ b/packages/stack/src/providers/theme-provider.tsx
@@ -69,7 +69,7 @@ function convertColorsToCSS(theme: Theme) {
.stack-scope {
${colorsToCSSVars(colors.light)}
}
- [data-stack-theme="dark"] .stack-scope {
+ html:has(head > [data-stack-theme="dark"]) .stack-scope {
${colorsToCSSVars(colors.dark)}
}`;
}
@@ -95,7 +95,6 @@ export function StackTheme({
<>