DEV Community

Cover image for I Integrated Better Auth into Nuxt, and the Session Bugs Taught Me More Than I Expected
Tito Candra
Tito Candra

Posted on

I Integrated Better Auth into Nuxt, and the Session Bugs Taught Me More Than I Expected

When I started building my blog with Nuxt, I wanted to keep things simple. I needed an authentication system for the admin panel, so I could manage articles, categories, and other content without exposing those features to everyone.

After looking at my options, I decided to use Better Auth.

At first, the integration seemed straightforward. I could configure the authentication client, create a login page, and connect it to my admin panel.

But as I started working with authentication across different pages and layouts, I ran into problems I didn't expect.

The login request could succeed, but parts of the application still behaved as if I wasn't logged in.

That was when I realized that getting authentication to work and keeping authentication state consistent across a Nuxt application were two different challenges.

The Login Worked, but Something Still Felt Wrong

One of the most confusing problems I encountered was session synchronization.

Imagine logging into your admin panel successfully. You expect the application to recognize that you're authenticated everywhere, right?

In my case, that wasn't always what happened.

The login process could succeed, but another layout or component could still show the old authentication state. In some cases, the UI behaved as if the user hadn't logged in yet.

Naturally, my first thought was that something was wrong with Better Auth itself.

I started looking at the authentication flow, checking how the session was retrieved, and investigating why different parts of the application didn't seem to agree about the current user.

What made this problem tricky was that not everything was actually broken.

Sometimes, the session existed, but the UI was still using outdated data.

That distinction changed how I approached the problem.

I Started Looking at Nuxt Instead of Just the Authentication Library

Before this experience, I tended to think of authentication as something relatively isolated: send credentials, receive a response, and update the UI.

Working with Better Auth in Nuxt made me pay more attention to how the framework manages data and application state.

Nuxt has server-side rendering (SSR), which means parts of the application can run on the server before the resulting HTML reaches the browser.

It also has its own data-fetching utilities, such as useFetch, which can reuse cached data.

These features are useful, but they introduce details that I needed to understand.

For example, when session data is fetched through Nuxt's data-fetching system, that data doesn't automatically become synchronized across every separate place where I request it.

If multiple layouts and pages manage their session data independently, they can end up working with different states.

That helped explain why one part of my application could recognize a successful login while another part still showed the previous state.

The problem wasn't necessarily the login request. It was how the application handled the session after that request.

A Composable Error Taught Me Something Important

I also encountered another issue while integrating Better Auth.

I initially tried to initialize the authentication client in a regular utility file and access Nuxt's runtime configuration from there.

Something like this looked reasonable to me:

const config = useRuntimeConfig()
Enter fullscreen mode Exit fullscreen mode

However, Nuxt composables such as useRuntimeConfig() and useNuxtApp() need the appropriate Nuxt context.

Calling them at the top level of a regular module doesn't guarantee that this context will be available.

The result was an error telling me that a composable requiring access to the Nuxt instance had been called outside that context.

At first, this felt like one of those framework-specific issues that was difficult to understand without spending time reading and experimenting.

Eventually, I learned to think differently about where application code should be initialized.

A regular utility function, a Nuxt composable, and a Nuxt plugin might all contain TypeScript code, but they don't necessarily run in the same context.

Moving the authentication client initialization into a Nuxt plugin gave it the appropriate place to access runtime configuration and expose the client to the rest of the application.

It was a small change in code organization, but it helped me understand Nuxt's conventions much better.

I also became more careful about which code should run on the server and which code should run in the browser.

The Biggest Lesson: One Source of Truth Matters

The session synchronization issue eventually led me to reconsider how I managed authentication state.

Instead of treating every layout and page as an independent place to retrieve the session, I started thinking about authentication as shared application state.

Nuxt provides useState() for sharing reactive state in an SSR-friendly way.

For my case, the idea was simple: keep the session in one shared state, let the relevant components read from it, and update that state when authentication changes.

After a successful sign-in or sign-out, the session needs to be refreshed so the rest of the application can reflect the new state.

This approach made more sense to me than allowing each component to maintain its own potentially outdated version of the session.

There was still an important detail to consider: shared state doesn't automatically guarantee that the session is initialized correctly. The initial session needs to be resolved at the appropriate time, especially when server-side rendering is involved.

But the overall design became clearer once I stopped treating session data as something every component should independently figure out.

It wasn't just about making the login page work anymore. It was about deciding which part of the application should own the authentication state and how other parts should consume it.

SSR Added Another Layer to the Problem

Another thing I learned was that server-side rendering changes how I think about requests and authentication.

When a user visits a page, Nuxt may render that page on the server. That server-side code can make its own requests, but it doesn't automatically behave exactly like code running in the user's browser.

For example, browser cookies are important when checking an existing session. When handling an internal server-side request, I needed to pay attention to whether the relevant cookie headers were being forwarded.

I also had to think about the authentication client's base URL. A relative URL that works naturally in the browser might not be sufficient in a server-side context.

These details were easy to overlook when I was focused only on the sign-in form.

They also helped me understand why authentication problems in SSR applications can be confusing: the browser, the server, the authentication library, and Nuxt's data-fetching system all play different roles.

Once I started separating those responsibilities, debugging became more systematic.

Instead of immediately changing the authentication configuration, I could ask more specific questions.

Was the login request successful? Was the session cookie present? Was the server receiving the expected headers? Was the session actually stale, or was the UI simply showing outdated data?

Those questions gave me a much better starting point.

What I Took Away From This Experience

I didn't expect authentication to teach me so much about how Nuxt works internally.

Looking back, the most useful lessons weren't about memorizing a particular Better Auth configuration.

They were about understanding the environment in which my code runs and how application state moves through the system.

Here are a few things I'm taking forward into my next projects:

  • Successful requests don't always mean the UI is up to date. I need to distinguish between a backend problem and a frontend state synchronization problem.
  • Nuxt composables need the right context. Where code is initialized matters, especially when working with runtime configuration and third-party libraries.
  • SSR introduces responsibilities that browser-only applications may not have. Cookies, request headers, and server-side URLs deserve more attention.
  • Shared state needs a clear owner. When several components depend on the same authentication session, they should not become disconnected sources of truth.
  • Debugging gets easier when I investigate one layer at a time. Checking the request, cookie, server response, and UI state separately is more useful than assuming everything is an authentication failure.

I'm still learning Nuxt, and I know there are more details to explore. But this experience gave me a better mental model of how authentication and SSR interact.

More importantly, it reminded me that integrating a library into a framework is about more than getting the initial setup right. I also need to understand how that library fits into the framework's existing lifecycle, state management, and rendering behavior.

Final Thoughts

Building this blog has been a useful way to learn Nuxt beyond the basics.

I could have studied plugins, useState(), SSR, and data fetching separately. But encountering a real problem made the connections between them much easier to understand.

The authentication bugs were frustrating at first, but they pushed me to look beyond the login form and think about how the application behaved as a whole.

That's something I want to keep doing as I learn new technologies: build something real, pay attention when things don't work as expected, and share what I learn along the way.

I'm curious how other Nuxt developers handle authentication state across layouts and pages, especially in applications that rely heavily on SSR.

Do you prefer a shared session composable, or do you use a different approach?

I'd love to hear what has worked for you.


This article is part of my journey of learning Nuxt by building a real project and sharing the lessons I discover along the way.

Top comments (1)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •

You need to verify your account.

Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to