[Docs][Content] - Updates Example Pages -> Vite Vanilla JS examples (#1050)

<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->

feat(docs): centralize code examples with dynamic variant tabs

This PR introduces a system for managing code examples centrally while
supporting file-based variant tabs (like html/script) within a single
code block.

## Changes

- **Extended variant system**: `PlatformCodeblock` now supports custom
variant names beyond just 'server'/'client', enabling tabs for any file
grouping (e.g., html/script pairs)
- **Vite example migration**: Moved vite-example.mdx code to
`code-examples/vite-example.ts` with html/script variants
- **LLM copy support**: The "Copy Markdown" button now expands
`PlatformCodeblock` components to inline the actual code, so LLMs
receive the full code examples instead of component references

## How it works

Code examples with variants are now displayed with filename-based tabs:
- Define examples with `variant: 'html'` and `variant: 'script'` in
`code-examples/`
- The tab labels automatically use the `filename` property
- When copying markdown for LLMs, all variants are included with their
filenames


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Release Notes

* **Documentation**
* Added a Vite JavaScript example guide with grouped auth examples
(init, index, password, OTP, OAuth) and wired examples into the
getting-started navigation.
* Removed the previous multi-page example guide and replaced it with the
new Vite-focused page.
* Documentation generation now expands platform code blocks to inline
concrete examples for clearer rendered docs.

* **Refactor**
* Improved code-example variant handling to support flexible variant
names for better tabbed/code-sample organization.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Madison 2025-12-22 08:53:51 -06:00 committed by GitHub
parent 8c4b8c5544
commit f3d0a68720
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 669 additions and 533 deletions

View File

@ -1,10 +1,12 @@
import { CodeExample } from '../lib/code-examples';
import { apiKeysExamples } from './api-keys';
import { setupExamples } from './setup';
import { viteExamples } from './vite-example';
const allExamples: Record<string, Record<string, Record<string, CodeExample[]>>> = {
'setup': setupExamples,
'apps': apiKeysExamples,
'getting-started': viteExamples,
// Add more sections here as needed:
// 'auth': authExamples,
// 'customization': customizationExamples,

View File

@ -0,0 +1,445 @@
import { CodeExample } from '../lib/code-examples';
export const viteExamples = {
'vite-example': {
'init-app': [
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
code: `import { StackClientApp } from "@stackframe/js";
// Add type declaration for Vite's import.meta.env
declare global {
interface ImportMeta {
env: {
VITE_STACK_API_URL: string;
VITE_STACK_PROJECT_ID: string;
VITE_STACK_PUBLISHABLE_CLIENT_KEY: string;
};
}
}
export const stackClientApp = new StackClientApp({
baseUrl: import.meta.env.VITE_STACK_API_URL,
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
tokenStore: "cookie",
urls: {
oauthCallback: window.location.origin + "/oauth",
},
});`,
highlightLanguage: 'typescript',
filename: 'stack/client.ts'
}
] as CodeExample[],
'index-page': [
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'html',
code: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stack Auth JS Examples</title>
</head>
<body>
<h1>Stack Auth JS Examples</h1>
<div id="authOptions">
<p>Choose an authentication example:</p>
<ul>
<li><a href="/password-sign-in">Sign in with Password</a></li>
<li><a href="/password-sign-up">Create Account with Password</a></li>
<li><a href="/otp-sign-in">Sign in with OTP Code</a></li>
<li><a href="/oauth">Sign in with Google</a></li>
</ul>
</div>
<div id="userInfo" style="display: none;">
<h2>User Information</h2>
<p>Email: <span id="userEmail"></span></p>
<button id="signOut">Sign Out</button>
</div>
<script type="module" src="/index-script.ts"></script>
</body>
</html>`,
highlightLanguage: 'html',
filename: 'index.html'
},
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'script',
code: `import { stackClientApp } from "./stack/client";
const updateUIState = (user: any | null) => {
const authOptions = document.getElementById("authOptions");
const userInfo = document.getElementById("userInfo");
const userEmailSpan = document.getElementById("userEmail");
if (user) {
if (authOptions) authOptions.style.display = "none";
if (userInfo) userInfo.style.display = "block";
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
} else {
if (authOptions) authOptions.style.display = "block";
if (userInfo) userInfo.style.display = "none";
}
};
// Check if user is already signed in
stackClientApp.getUser().then(updateUIState);
// Handle Sign Out
document.getElementById("signOut")?.addEventListener("click", async () => {
const user = await stackClientApp.getUser();
if (user) {
await user.signOut();
updateUIState(null);
}
});`,
highlightLanguage: 'typescript',
filename: 'index-script.ts'
}
] as CodeExample[],
'password-sign-in': [
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'html',
code: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Sign In</title>
</head>
<body>
<h1>Password Sign In</h1>
<p><a href="/"> Back to home</a></p>
<div id="loginForm">
<h2>Sign In</h2>
<input type="email" id="emailInput" placeholder="Email" />
<input type="password" id="passwordInput" placeholder="Password" />
<button id="signIn">Sign In</button>
<div>
<p>Don't have an account? <a href="/password-sign-up">Create account</a></p>
</div>
</div>
<script type="module" src="/password-sign-in-script.ts"></script>
</body>
</html>`,
highlightLanguage: 'html',
filename: 'password-sign-in.html'
},
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'script',
code: `import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
document.getElementById("showSignUp")?.addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("loginForm")?.classList.add("hidden");
document.getElementById("signUpForm")?.classList.remove("hidden");
});
document.getElementById("showSignIn")?.addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("loginForm")?.classList.remove("hidden");
document.getElementById("signUpForm")?.classList.add("hidden");
});
document.getElementById("signIn")?.addEventListener("click", async () => {
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
const passwordInput = document.getElementById("passwordInput") as HTMLInputElement;
const result = await stackClientApp.signInWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (result.status === "error") {
alert("Sign in failed. Please check your email and password and try again.");
} else {
window.location.href = "/";
}
});
document.getElementById("signUp")?.addEventListener("click", async () => {
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
const result = await stackClientApp.signUpWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (result.status === "error") {
alert("Sign up failed. Please try again.");
return;
}
const signInResult = await stackClientApp.signInWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (signInResult.status === "error") {
alert("Account created but sign in failed. Please sign in manually.");
} else {
window.location.href = "/";
}
});`,
highlightLanguage: 'typescript',
filename: 'password-sign-in-script.ts'
}
] as CodeExample[],
'password-sign-up': [
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'html',
code: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Sign Up</title>
</head>
<body>
<h1>Password Sign Up</h1>
<p><a href="/"> Back to home</a></p>
<div id="signUpForm">
<h2>Sign Up</h2>
<input type="email" id="signUpEmail" placeholder="Email" />
<input type="password" id="signUpPassword" placeholder="Password" />
<button id="signUp">Sign Up</button>
<div>
<p>Already have an account? <a href="/password-sign-in">Sign in</a></p>
</div>
</div>
<script type="module" src="/password-sign-up-script.ts"></script>
</body>
</html>`,
highlightLanguage: 'html',
filename: 'password-sign-up.html'
},
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'script',
code: `import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
document.getElementById("signUp")?.addEventListener("click", async () => {
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
const result = await stackClientApp.signUpWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (result.status === "error") {
alert("Sign up failed. Please try again.");
return;
}
const signInResult = await stackClientApp.signInWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (signInResult.status === "error") {
alert("Account created but sign in failed. Please sign in manually.");
window.location.href = "/password-sign-in";
} else {
window.location.href = "/";
}
});`,
highlightLanguage: 'typescript',
filename: 'password-sign-up-script.ts'
}
] as CodeExample[],
'otp-sign-in': [
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'html',
code: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OTP Sign In</title>
</head>
<body>
<h1>OTP Sign In</h1>
<p><a href="/"> Back to home</a></p>
<div id="otpForm">
<h2>Sign In with Email Code</h2>
<div id="emailStep">
<input type="email" id="emailInput" placeholder="Email" />
<button id="sendCode">Send Code</button>
</div>
<div id="codeStep" style="display: none;">
<p>Enter the code sent to your email</p>
<input type="text" id="codeInput" placeholder="Enter code" />
<button id="verifyCode">Verify Code</button>
</div>
</div>
<script type="module" src="/otp-sign-in-script.ts"></script>
</body>
</html>`,
highlightLanguage: 'html',
filename: 'otp-sign-in.html'
},
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'script',
code: `import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
document.getElementById("sendCode")?.addEventListener("click", async () => {
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
await stackClientApp.sendMagicLinkEmail({
email: emailInput.value,
});
document.getElementById("emailStep")!.style.display = "none";
document.getElementById("codeStep")!.style.display = "block";
});
document.getElementById("verifyCode")?.addEventListener("click", async () => {
const codeInput = document.getElementById("codeInput") as HTMLInputElement;
const result = await stackClientApp.signInWithMagicLink({
code: codeInput.value,
});
if (result.status === "error") {
alert("Verification failed. Please check the code and try again.");
} else {
window.location.href = "/";
}
});`,
highlightLanguage: 'typescript',
filename: 'otp-sign-in-script.ts'
}
] as CodeExample[],
'oauth': [
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'html',
code: `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OAuth Authentication</title>
<style>
.hidden {
display: none;
}
</style>
</head>
<body>
<h1>OAuth Authentication</h1>
<p><a href="/"> Back to home</a></p>
<div id="loginButtons">
<h2>Sign In with OAuth</h2>
<button id="googleSignIn">Sign in with Google</button>
</div>
<script type="module" src="/oauth-script.ts"></script>
</body>
</html>`,
highlightLanguage: 'html',
filename: 'oauth.html'
},
{
language: 'JavaScript',
framework: 'Vanilla JavaScript',
variant: 'script',
code: `import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
// Handle Google Sign In
document.getElementById("googleSignIn")?.addEventListener("click", async () => {
try {
await stackClientApp.signInWithOAuth('google');
} catch (error) {
console.error("Google sign in failed:", error);
alert("Failed to initialize Google sign in");
}
});
// Handle OAuth redirect
window.addEventListener("load", async () => {
try {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const state = params.get("state");
if (code && state) {
const user = await stackClientApp.callOAuthCallback();
if (user) {
window.location.href = "/";
}
}
} catch (error) {
console.error("Failed to handle OAuth redirect:", error);
alert("Authentication failed. Please try again.");
}
});`,
highlightLanguage: 'typescript',
filename: 'oauth-script.ts'
}
] as CodeExample[],
}
};

View File

@ -1,453 +0,0 @@
---
title: Example-pages
---
This guide demonstrates how to integrate Stack Auth with Vite. The same principles apply to other JavaScript frameworks as well. You can find the complete example code in our [GitHub repository](https://github.com/stack-auth/stack-auth/tree/main/examples/js-example).
### Initialize the app
```typescript title="stack/client.ts"
import { StackClientApp } from "@stackframe/js";
// Add type declaration for Vite's import.meta.env
declare global {
interface ImportMeta {
env: {
VITE_STACK_API_URL: string;
VITE_STACK_PROJECT_ID: string;
VITE_STACK_PUBLISHABLE_CLIENT_KEY: string;
};
}
}
export const stackClientApp = new StackClientApp({
baseUrl: import.meta.env.VITE_STACK_API_URL,
projectId: import.meta.env.VITE_STACK_PROJECT_ID,
publishableClientKey: import.meta.env.VITE_STACK_PUBLISHABLE_CLIENT_KEY,
tokenStore: "cookie",
urls: {
oauthCallback: window.location.origin + "/oauth",
},
});
```
### Index page with user information
<Tabs defaultValue="html">
<TabsList>
<TabsTrigger value="html">index.html</TabsTrigger>
<TabsTrigger value="script">index-script.ts</TabsTrigger>
</TabsList>
<TabsContent value="html">
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stack Auth JS Examples</title>
</head>
<body>
<h1>Stack Auth JS Examples</h1>
<div id="authOptions">
<p>Choose an authentication example:</p>
<ul>
<li><a href="/password-sign-in">Sign in with Password</a></li>
<li><a href="/password-sign-up">Create Account with Password</a></li>
<li><a href="/otp-sign-in">Sign in with OTP Code</a></li>
<li><a href="/oauth">Sign in with Google</a></li>
</ul>
</div>
<div id="userInfo" style="display: none;">
<h2>User Information</h2>
<p>Email: <span id="userEmail"></span></p>
<button id="signOut">Sign Out</button>
</div>
<script type="module" src="/index-script.ts"></script>
</body>
</html>
```
</TabsContent>
<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack/client";
const updateUIState = (user: any | null) => {
const authOptions = document.getElementById("authOptions");
const userInfo = document.getElementById("userInfo");
const userEmailSpan = document.getElementById("userEmail");
if (user) {
if (authOptions) authOptions.style.display = "none";
if (userInfo) userInfo.style.display = "block";
if (userEmailSpan) userEmailSpan.textContent = user.primaryEmail || "";
} else {
if (authOptions) authOptions.style.display = "block";
if (userInfo) userInfo.style.display = "none";
}
};
// Check if user is already signed in
stackClientApp.getUser().then(updateUIState);
// Handle Sign Out
document.getElementById("signOut")?.addEventListener("click", async () => {
const user = await stackClientApp.getUser();
if (user) {
await user.signOut();
updateUIState(null);
}
});
```
</TabsContent>
</Tabs>
### Sign in with password
<Tabs defaultValue="html">
<TabsList>
<TabsTrigger value="html">password-sign-in.html</TabsTrigger>
<TabsTrigger value="script">password-sign-in-script.ts</TabsTrigger>
</TabsList>
<TabsContent value="html">
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Sign In</title>
</head>
<body>
<h1>Password Sign In</h1>
<p><a href="/">← Back to home</a></p>
<div id="loginForm">
<h2>Sign In</h2>
<input type="email" id="emailInput" placeholder="Email" />
<input type="password" id="passwordInput" placeholder="Password" />
<button id="signIn">Sign In</button>
<div>
<p>Don't have an account? <a href="/password-sign-up">Create account</a></p>
</div>
</div>
<script type="module" src="/password-sign-in-script.ts"></script>
</body>
</html>
```
</TabsContent>
<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
document.getElementById("showSignUp")?.addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("loginForm")?.classList.add("hidden");
document.getElementById("signUpForm")?.classList.remove("hidden");
});
document.getElementById("showSignIn")?.addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("loginForm")?.classList.remove("hidden");
document.getElementById("signUpForm")?.classList.add("hidden");
});
document.getElementById("signIn")?.addEventListener("click", async () => {
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
const passwordInput = document.getElementById("passwordInput") as HTMLInputElement;
const result = await stackClientApp.signInWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (result.status === "error") {
alert("Sign in failed. Please check your email and password and try again.");
} else {
window.location.href = "/";
}
});
document.getElementById("signUp")?.addEventListener("click", async () => {
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
const result = await stackClientApp.signUpWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (result.status === "error") {
alert("Sign up failed. Please try again.");
return;
}
const signInResult = await stackClientApp.signInWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (signInResult.status === "error") {
alert("Account created but sign in failed. Please sign in manually.");
} else {
window.location.href = "/";
}
});
```
</TabsContent>
</Tabs>
### Sign up with password
<Tabs defaultValue="html">
<TabsList>
<TabsTrigger value="html">password-sign-up.html</TabsTrigger>
<TabsTrigger value="script">password-sign-up-script.ts</TabsTrigger>
</TabsList>
<TabsContent value="html">
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Sign Up</title>
</head>
<body>
<h1>Password Sign Up</h1>
<p><a href="/">← Back to home</a></p>
<div id="signUpForm">
<h2>Sign Up</h2>
<input type="email" id="signUpEmail" placeholder="Email" />
<input type="password" id="signUpPassword" placeholder="Password" />
<button id="signUp">Sign Up</button>
<div>
<p>Already have an account? <a href="/password-sign-in">Sign in</a></p>
</div>
</div>
<script type="module" src="/password-sign-up-script.ts"></script>
</body>
</html>
```
</TabsContent>
<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
document.getElementById("signUp")?.addEventListener("click", async () => {
const emailInput = document.getElementById("signUpEmail") as HTMLInputElement;
const passwordInput = document.getElementById("signUpPassword") as HTMLInputElement;
const result = await stackClientApp.signUpWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (result.status === "error") {
alert("Sign up failed. Please try again.");
return;
}
const signInResult = await stackClientApp.signInWithCredential({
email: emailInput.value,
password: passwordInput.value,
});
if (signInResult.status === "error") {
alert("Account created but sign in failed. Please sign in manually.");
window.location.href = "/password-sign-in";
} else {
window.location.href = "/";
}
});
```
</TabsContent>
</Tabs>
### Sign in with OTP/Magic Link
<Tabs defaultValue="html">
<TabsList>
<TabsTrigger value="html">otp-sign-in.html</TabsTrigger>
<TabsTrigger value="script">otp-sign-in-script.ts</TabsTrigger>
</TabsList>
<TabsContent value="html">
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OTP Sign In</title>
</head>
<body>
<h1>OTP Sign In</h1>
<p><a href="/">← Back to home</a></p>
<div id="otpForm">
<h2>Sign In with Email Code</h2>
<div id="emailStep">
<input type="email" id="emailInput" placeholder="Email" />
<button id="sendCode">Send Code</button>
</div>
<div id="codeStep" style="display: none;">
<p>Enter the code sent to your email</p>
<input type="text" id="codeInput" placeholder="Enter code" />
<button id="verifyCode">Verify Code</button>
</div>
</div>
<script type="module" src="/otp-sign-in-script.ts"></script>
</body>
</html>
```
</TabsContent>
<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
document.getElementById("sendCode")?.addEventListener("click", async () => {
const emailInput = document.getElementById("emailInput") as HTMLInputElement;
await stackClientApp.sendMagicLinkEmail({
email: emailInput.value,
});
document.getElementById("emailStep")!.style.display = "none";
document.getElementById("codeStep")!.style.display = "block";
});
document.getElementById("verifyCode")?.addEventListener("click", async () => {
const codeInput = document.getElementById("codeInput") as HTMLInputElement;
const result = await stackClientApp.signInWithMagicLink({
code: codeInput.value,
});
if (result.status === "error") {
alert("Verification failed. Please check the code and try again.");
} else {
window.location.href = "/";
}
});
```
</TabsContent>
</Tabs>
### OAuth sign in
<Tabs defaultValue="html">
<TabsList>
<TabsTrigger value="html">oauth.html</TabsTrigger>
<TabsTrigger value="script">oauth-script.ts</TabsTrigger>
</TabsList>
<TabsContent value="html">
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OAuth Authentication</title>
<style>
.hidden {
display: none;
}
</style>
</head>
<body>
<h1>OAuth Authentication</h1>
<p><a href="/">← Back to home</a></p>
<div id="loginButtons">
<h2>Sign In with OAuth</h2>
<button id="googleSignIn">Sign in with Google</button>
</div>
<script type="module" src="/oauth-script.ts"></script>
</body>
</html>
```
</TabsContent>
<TabsContent value="script">
```typescript
import { stackClientApp } from "./stack/client";
// Check if user is already signed in
stackClientApp.getUser().then((user) => {
if (user) {
window.location.href = "/";
}
});
// Handle Google Sign In
document.getElementById("googleSignIn")?.addEventListener("click", async () => {
try {
await stackClientApp.signInWithOAuth('google');
} catch (error) {
console.error("Google sign in failed:", error);
alert("Failed to initialize Google sign in");
}
});
// Handle OAuth redirect
window.addEventListener("load", async () => {
try {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const state = params.get("state");
if (code && state) {
const user = await stackClientApp.callOAuthCallback();
if (user) {
window.location.href = "/";
}
}
} catch (error) {
console.error("Failed to handle OAuth redirect:", error);
alert("Authentication failed. Please try again.");
}
});
```
</TabsContent>
</Tabs>

View File

@ -0,0 +1,48 @@
---
title: Vite JavaScript Example
---
This guide demonstrates how to integrate Stack Auth with Vite. The same principles apply to other JavaScript frameworks as well. You can find the complete example code in our [GitHub repository](https://github.com/stack-auth/stack-auth/tree/main/examples/js-example).
### Initialize the app
<PlatformCodeblock
document="getting-started/vite-example"
examples={["init-app"]}
/>
### Index page with user information
<PlatformCodeblock
document="getting-started/vite-example"
examples={["index-page"]}
/>
### Sign in with password
<PlatformCodeblock
document="getting-started/vite-example"
examples={["password-sign-in"]}
/>
### Sign up with password
<PlatformCodeblock
document="getting-started/vite-example"
examples={["password-sign-up"]}
/>
### Sign in with OTP/Magic Link
<PlatformCodeblock
document="getting-started/vite-example"
examples={["otp-sign-in"]}
/>
### OAuth sign in
<PlatformCodeblock
document="getting-started/vite-example"
examples={["oauth"]}
/>

View File

@ -10,7 +10,7 @@
"getting-started/components",
"getting-started/users",
"getting-started/production",
"getting-started/example-pages",
"getting-started/vite-example",
"---Apps---",
"apps/api-keys",
"apps/emails",

View File

@ -15,7 +15,8 @@
export type CodeExample = {
language: string;
framework: string;
variant?: 'server' | 'client';
/** Variant for tabbed code within the same platform/framework (e.g., 'server'/'client' or 'html'/'script') */
variant?: string;
code: string;
highlightLanguage: string;
filename?: string;

View File

@ -5,6 +5,8 @@ import { join } from 'path';
import { remark } from 'remark';
import remarkGfm from 'remark-gfm';
import remarkMdx from 'remark-mdx';
import { getExample } from '../code-examples';
import type { CodeExample } from './code-examples';
import { apiSource, source } from './source';
const processor = remark()
@ -150,6 +152,71 @@ function formatOpenAPISpec(spec: any): string {
return result;
}
// Helper function to format a code example as markdown
function formatCodeExample(example: CodeExample): string {
const filename = example.filename ? ` title="${example.filename}"` : '';
return `\`\`\`${example.highlightLanguage}${filename}\n${example.code}\n\`\`\``;
}
// Helper function to replace PlatformCodeblock components with actual code
function expandPlatformCodeblocks(content: string): string {
// Match <PlatformCodeblock document="..." examples={[...]} /> or multi-line versions
const platformCodeblockRegex = /<PlatformCodeblock\s+([^>]*?)\/>/gs;
return content.replace(platformCodeblockRegex, (match, propsString) => {
// Extract document prop
const documentMatch = propsString.match(/document=["']([^"']+)["']/);
// Extract examples prop - handle both {["..."]} and {["...", "..."]} formats
const examplesMatch = propsString.match(/examples=\{\s*\[([^\]]+)\]\s*\}/);
if (!documentMatch || !examplesMatch) {
// Can't parse, return original
return match;
}
const documentPath = documentMatch[1];
// Parse the examples array - handle quoted strings
const examplesStr = examplesMatch[1];
const exampleNames = examplesStr
.split(',')
.map((s: string) => s.trim().replace(/^["']|["']$/g, ''));
let result = '';
for (const exampleName of exampleNames) {
const examples = getExample(documentPath, exampleName);
if (!examples || examples.length === 0) {
result += `<!-- Code example "${exampleName}" not found -->\n`;
continue;
}
// Group examples by variant if they have variants
const hasVariants = examples.some(e => e.variant);
if (hasVariants) {
// Show all variants
for (const example of examples) {
if (example.filename) {
result += `**${example.filename}:**\n\n`;
}
result += formatCodeExample(example) + '\n\n';
}
} else {
// No variants - just show the first example (typically there's only one per framework)
// For LLM context, show all framework variants
for (const example of examples) {
if (examples.length > 1 && example.framework) {
result += `**${example.framework}:**\n\n`;
}
result += formatCodeExample(example) + '\n\n';
}
}
}
return result;
});
}
export async function getLLMText(page: InferPageType<typeof source> | InferPageType<typeof apiSource>) {
try {
// Check if this is an API page and extract OpenAPI content
@ -172,6 +239,9 @@ ${openAPIContent}`;
''
);
// Expand PlatformCodeblock components to inline code
content = expandPlatformCodeblocks(content);
const processed = await processor.process({
path: page.data._file.absolutePath,
value: content,

View File

@ -11,7 +11,7 @@ import { BaseCodeblock } from './base-codeblock';
type PlatformChangeListener = (platform: string) => void;
type FrameworkChangeListener = (platform: string, framework: string) => void;
type VariantSelections = Partial<Record<string, Partial<Record<string, 'server' | 'client'>>>>;
type VariantSelections = Partial<Record<string, Partial<Record<string, string>>>>;
const platformListeners = new Map<string, PlatformChangeListener[]>();
const frameworkListeners = new Map<string, FrameworkChangeListener[]>();
@ -126,6 +126,14 @@ export type PlatformCodeblockProps = {
className?: string,
}
type VariantConfig = {
code: string,
language?: string,
filename?: string,
};
type FrameworkConfig = VariantConfig | { [variantName: string]: VariantConfig };
/**
* Converts CodeExample[] from code-examples.ts to the platforms format
* Only includes platforms/frameworks defined in the global config
@ -133,27 +141,14 @@ export type PlatformCodeblockProps = {
function convertExamplesToPlatforms(examples: CodeExample[]) {
const platforms: {
[platformName: string]: {
[frameworkName: string]: {
code: string,
language?: string,
filename?: string,
} | {
server: {
code: string,
language?: string,
filename?: string,
},
client: {
code: string,
language?: string,
filename?: string,
},
},
[frameworkName: string]: FrameworkConfig,
},
} = {};
const defaultFrameworks: { [platformName: string]: string } = {};
const defaultVariants: VariantSelections = {};
// Track which variants exist for each platform/framework
const variantKeys: { [platform: string]: { [framework: string]: string[] } } = {};
// Initialize default frameworks from global config
for (const platformConfig of PLATFORMS) {
@ -180,34 +175,38 @@ function convertExamplesToPlatforms(examples: CodeExample[]) {
}
if (variant) {
// Has server/client variant - initialize if not already a variant config
// We check if 'server' exists to determine if it's already been initialized as a variant config
if (!('server' in (platforms[language][framework] ?? {}))) {
platforms[language][framework] = {
server: { code: '', language: highlightLanguage },
client: { code: '', language: highlightLanguage }
};
// Has variant - initialize tracking if needed
if (!(language in variantKeys)) {
variantKeys[language] = {};
}
if (!(framework in variantKeys[language])) {
variantKeys[language][framework] = [];
}
const variantConfig = platforms[language][framework] as {
server: { code: string, language?: string, filename?: string },
client: { code: string, language?: string, filename?: string },
};
// Track this variant key
if (!variantKeys[language][framework].includes(variant)) {
variantKeys[language][framework].push(variant);
}
// Explicitly narrow the variant type
const variantType: 'server' | 'client' = variant;
variantConfig[variantType] = {
// Initialize framework config as variant object if needed
const existingConfig = platforms[language][framework] as FrameworkConfig | undefined;
if (existingConfig === undefined || 'code' in existingConfig) {
platforms[language][framework] = {};
}
const variantConfig = platforms[language][framework] as { [key: string]: VariantConfig };
variantConfig[variant] = {
code,
language: highlightLanguage,
filename
};
// Initialize default variants
// Initialize default variants (use first variant seen)
if (!(language in defaultVariants)) {
defaultVariants[language] = {};
}
if (!defaultVariants[language]?.[framework]) {
defaultVariants[language]![framework] = 'server';
defaultVariants[language]![framework] = variant;
}
} else {
// No variant
@ -233,7 +232,7 @@ function convertExamplesToPlatforms(examples: CodeExample[]) {
? DEFAULT_PLATFORM
: Object.keys(sortedPlatforms)[0];
return { platforms: sortedPlatforms, defaultPlatform, defaultFrameworks, defaultVariants };
return { platforms: sortedPlatforms, defaultPlatform, defaultFrameworks, defaultVariants, variantKeys };
}
export function PlatformCodeblock({
@ -255,9 +254,9 @@ export function PlatformCodeblock({
}
// Convert to the internal platforms format
const { platforms, defaultPlatform, defaultFrameworks, defaultVariants } = allExamples.length > 0
const { platforms, defaultPlatform, defaultFrameworks, defaultVariants, variantKeys } = allExamples.length > 0
? convertExamplesToPlatforms(allExamples)
: { platforms: {}, defaultPlatform: '', defaultFrameworks: {}, defaultVariants: {} };
: { platforms: {}, defaultPlatform: '', defaultFrameworks: {}, defaultVariants: {}, variantKeys: {} };
const platformNames = Object.keys(platforms);
const firstPlatform = defaultPlatform || platformNames[0];
@ -366,41 +365,58 @@ export function PlatformCodeblock({
const currentFrameworks = Object.keys(platforms[selectedPlatform] ?? {});
const currentFramework = selectedFrameworks[selectedPlatform] || currentFrameworks[0];
// Helper functions for server/client variants
const hasVariants = (platform: string, framework: string) => {
const platformConfig = platforms[platform];
const config = platformConfig[framework];
if (typeof config !== 'object') {
return false;
}
return 'server' in config && 'client' in config;
// Helper functions for variants (supports dynamic variant names like 'server'/'client' or 'html'/'script')
const getVariantKeys = (platform: string, framework: string): string[] => {
if (!(platform in variantKeys)) return [];
const platformVariants = variantKeys[platform];
if (!(framework in platformVariants)) return [];
return platformVariants[framework];
};
const getCurrentVariant = (): 'server' | 'client' => {
const hasVariants = (platform: string, framework: string) => {
return getVariantKeys(platform, framework).length > 0;
};
const getCurrentVariant = (): string => {
const platformVariants = selectedVariants[selectedPlatform];
return platformVariants?.[currentFramework] ?? 'server';
const keys = getVariantKeys(selectedPlatform, currentFramework);
const selectedVariant = platformVariants?.[currentFramework];
if (selectedVariant) return selectedVariant;
return keys[0] || '';
};
const getCurrentCodeConfig = () => {
// Get platform config - may be undefined if platform was switched to one not in this block
const platformConfig = platforms[selectedPlatform] as typeof platforms[string] | undefined;
if (!platformConfig) {
return null;
// Get platform config - fall back to first available platform if selected doesn't exist
let platformToUse = selectedPlatform;
if (!(platformToUse in platforms)) {
// Selected platform doesn't exist in this code block, fall back to first available
platformToUse = platformNames[0];
if (!platformToUse) {
return null;
}
}
const platformConfig = platforms[platformToUse];
// Get framework config - fall back to first available framework if selected doesn't exist
let frameworkToUse = currentFramework;
const availableFrameworks = Object.keys(platformConfig);
if (!(frameworkToUse in platformConfig)) {
// Selected framework doesn't exist, fall back to first available
frameworkToUse = availableFrameworks[0];
if (!frameworkToUse) {
return null;
}
}
const config = platformConfig[frameworkToUse];
if (hasVariants(platformToUse, frameworkToUse)) {
const keys = getVariantKeys(platformToUse, frameworkToUse);
const platformVariants = selectedVariants[platformToUse];
const variant = platformVariants?.[frameworkToUse] || keys[0] || '';
return (config as { [key: string]: VariantConfig })[variant];
}
// Get the config for the current framework - may be undefined
const config = platformConfig[currentFramework] as typeof platformConfig[string] | undefined;
if (!config) {
return null;
}
if (hasVariants(selectedPlatform, currentFramework)) {
const variant = getCurrentVariant();
return (config as { server: { code: string, language?: string, filename?: string }, client: { code: string, language?: string, filename?: string } })[variant];
}
return config as { code: string, language?: string, filename?: string };
return config as VariantConfig;
};
const currentCodeConfig = getCurrentCodeConfig();
@ -500,7 +516,7 @@ export function PlatformCodeblock({
});
};
const handleVariantChange = (variant: 'server' | 'client') => {
const handleVariantChange = (variant: string) => {
setSelectedVariants(prev => ({
...prev,
[selectedPlatform]: {
@ -615,20 +631,27 @@ export function PlatformCodeblock({
hasVariants(selectedPlatform, currentFramework) ? (
<div className="mb-3 flex">
<div className="inline-flex items-center gap-1 rounded-full border border-fd-border/60 bg-fd-muted/20 p-1">
{(['server', 'client'] as const).map((variant) => (
<button
key={variant}
onClick={() => handleVariantChange(variant)}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-3 py-1 text-xs font-medium transition-colors duration-150",
getCurrentVariant() === variant
? "bg-fd-background text-fd-foreground shadow-sm border border-fd-border"
: "border border-transparent text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-muted/40"
)}
>
{variant.charAt(0).toUpperCase() + variant.slice(1)}
</button>
))}
{getVariantKeys(selectedPlatform, currentFramework).map((variant) => {
// Get the filename for this variant to use as label
const config = platforms[selectedPlatform][currentFramework] as { [key: string]: VariantConfig } | undefined;
const variantConfig = config?.[variant];
const label = variantConfig?.filename || variant.charAt(0).toUpperCase() + variant.slice(1);
return (
<button
key={variant}
onClick={() => handleVariantChange(variant)}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-3 py-1 text-xs font-medium transition-colors duration-150",
getCurrentVariant() === variant
? "bg-fd-background text-fd-foreground shadow-sm border border-fd-border"
: "border border-transparent text-fd-muted-foreground hover:text-fd-foreground hover:bg-fd-muted/40"
)}
>
{label}
</button>
);
})}
</div>
</div>
) : undefined