Getting Started

Users

Reading and writing user information, and protecting pages

In the last guide, we initialized Stack. This created new files containing a StackServerApp and StackProvider. In this section, we will show you how to utilize those for accessing and modifying the current user information on Server Components and Client Components, respectively.

Client Component basics

The useUser() hook returns the current user in a Client Component. By default, it will return null if the user is not signed in.

my-client-component.tsx
1"use client";
2import { useUser } from "@stackframe/stack"
3
4export function MyClientComponent() {
5 const user = useUser();
6 return <div>{user ? `Hello, ${user.displayName ?? "anon"}` : 'You are not logged in'}</div>;
7}

The useUser() hook is simply a shorthand for useStackApp().useUser(). useStackApp() also contains other useful hooks and methods for clients, which will be described later.

Sometimes, you want to retrieve the user only if they’re signed in, and redirect to the sign-in page otherwise. In this case, simply pass { or: "redirect" }, and the function will never return null.

1 const user = useUser({ or: "redirect" });
2 return <div>{`Hello, ${user.displayName ?? "anon"}`}</div>;

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():

my-server-component.tsx
1import { stackServerApp } from "@/stack";
2
3export default async function MyServerComponent() {
4 const user = await stackServerApp.getUser(); // or: stackServerApp.getUser({ or: "redirect" })
5 return <div>{user ? `Hello, ${user.displayName ?? "anon"}` : 'You are not logged in'}</div>;
6}

Since useUser() is a hook, it will re-render the component on user changes (eg. signout), while getUser() will only fetch the user once (on page load). You can also call useStackApp().getUser() on the client side to get the user in a non-component context.

Protecting a page

There are three ways to protect a page: in Client Components with useUser({ or: "redirect" }), in Server Components with await getUser({ or: "redirect" }), or with middleware.

On Client Components, the useUser({ or: 'redirect' }) hook will redirect the user to the sign-in page if they are not logged in. Similarly, on Server Components, call await getUser({ or: "redirect" }) to protect a page (or component).

Middleware can be used whenever it is easy to tell whether a page should be protected given just the URL, for example, when you have a /private section only accessible to logged-in users.

middleware.tsx
1export async function middleware(request: NextRequest) {
2 // You can add your own route protection logic here
3 const user = await stackServerApp.getUser();
4 if (!user) {
5 return NextResponse.redirect(new URL('/handler/sign-in', request.url));
6 }
7 return NextResponse.next();
8}

If you have sensitive information hidden in the page HTML itself, be aware of Next.js differences when using Server vs. Client Components.

  • Client Components: Client components are always sent to the browser, regardless of page protection. This is standard Next.js behavior. For more information, please refer to the Next.js documentation.

  • Server Components: If a component is protected, it is guaranteed that its bundled HTML will not be sent to the browser if the user is not logged in. However, this is not necessarily true for its children and the rest of the page, as Next.js may split components on the same page and send them to the client separately for performance.

    For example, if your page is <Parent><Child /></Parent>, where Parent is protected and Child is not, Next.js may still send <Child /> to the browser even if the user is not logged in. (Normal browsers will never display it, but attackers may be able to retrieve it.) Notably, this also applies to unprotected pages inside protected layouts.

    To remediate this, every component/page that contains sensitive information should protect itself, instead of relying on an outer layout. This is good practice anyways; it prevents you from accidentally exposing the data.

  • Middleware: Because middleware runs on the edge, it ensures that the protected URLs are not accessible to anyone who is not authorized, so you don’t have to worry about Next.js pre-sending unprotected components to the client.

Irregardless of which method you use, attackers will never be able to, say, impersonate a user.

User metadata

You can update attributes on a user object with the user.update() function.

my-client-component.tsx
1'use client';
2import { useUser } from "@stackframe/stack";
3
4export default function MyClientComponent() {
5 const user = useUser();
6 return <button onClick={async () => await user.update({ displayName: "New Name" })}>
7 Change Name
8 </button>;
9}

You can store custom data in the clientMetadata field. It will be readable and writable by the user themselves and should not contain any sensitive information.

1await user.update({
2 clientMetadata: {
3 mailingAddress: "123 Main St",
4 },
5});

You can then read the clientMetadata field from the User object:

1const user = useUser();
2console.log(user.clientMetadata);

If you want to store sensitive information, you can use the serverMetadata field. This data is only readable & writable from the server.

my-server-component.tsx
1const user = await stackServerApp.getUser();
2await user.update({
3 serverMetadata: {
4 secretInfo: "This is a secret",
5 },
6});
7
8// later:
9const user = await stackServerApp.getUser();
10console.log(user.serverMetadata);

Signing out

You can sign out the user by redirecting them to /handler/sign-out or simply by calling user.signOut(). They will be redirected to the URL configured as afterSignOut in the StackServerApp.

sign-out-button.tsx
1"use client";
2import { useUser } from "@stackframe/stack";
3
4export default function SignOutButton() {
5 const user = useUser();
6 return user ? <button onClick={() => user.signOut()}>Sign Out</button> : "Not signed in";
7}

Example: Custom profile page

Stack automatically creates a user profile on sign-up. Let’s build a page that displays this information. In app/profile/page.tsx:

app/profile/page.tsx
1'use client';
2import { useUser, useStackApp, UserButton } from "@stackframe/stack";
3
4export default function PageClient() {
5 const user = useUser();
6 const app = useStackApp();
7 return (
8 <div>
9 {user ? (
10 <div>
11 <UserButton />
12 <p>Welcome, {user.displayName ?? "anonymous user"}</p>
13 <p>Your e-mail: {user.primaryEmail}</p>
14 <button onClick={() => user.signOut()}>Sign Out</button>
15 </div>
16 ) : (
17 <div>
18 <p>You are not logged in</p>
19 <button onClick={() => app.redirectToSignIn()}>Sign in</button>
20 <button onClick={() => app.redirectToSignUp()}>Sign up</button>
21 </div>
22 )}
23 </div>
24 );
25}

After saving your code, you can see the profile page on http://localhost:3000/profile.

For more examples on how to use the User object, check the CurrentUser object in the SDK documentation.

Next steps

In the next guide, we will show you how to put your application into production.