js-sdk: Can't set up properly Next.js's SSR
I’ve followed the examples displayed in the README, as well as looked for solutions in other frameworks but I simply can’t fix this problem.
Doing a little bit of debug and research, I can come to the conclusion that the cookie contained by the request may be not what the authStore
expects, because, according to the examples, the key pb_auth=
is set as the default, while my cookie has the following structure:
a_session_[code]_legacy=[token]
Also, if it has anything to do with this, the token stored in localStorage it’s not equal to the one served on the cookie.
As extra information, I leave the code I wrote.
type ServerRequest = IncomingMessage & {
cookies: Partial<{
[key: string]: string
}>
}
class CustomAuthStore extends BaseAuthStore {
public request: ServerRequest
public response: ServerResponse<IncomingMessage>
constructor (request: ServerRequest, response: ServerResponse<IncomingMessage>) {
super()
this.request = request
this.response = response
this.loadFromCookie(
this.request.headers.cookie ?? ''
)
}
save (token: string, model: User | Admin | null): void {
super.save(token, model)
this.response.setHeader('set-cookie', this.exportToCookie())
}
clear (): void {
super.clear()
this.response.setHeader('set-cookie', this.exportToCookie())
}
}
Versions: pocketbase v0.7.10; pocketbase (sdk) v0.7.4
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 40 (5 by maintainers)
Hey, I have written functions returning PocketBase instances for both Server and Client components. I have done it in a style
@supabase/ssr
does it for ease of understandingFor client components : This instantiates a PocketBase instance using the singleton pattern, so you can call it anywhere you want. It also syncs the cookie with localStorage, thus seamlessly responding to auth state change
createBrowserClient
For server components : This instantiates a PocketBase instance for each call, you can pass it a cookieStore, in which case you will get the authStore instantiated. For static routes, you can use it without passing any cookieStore.
createServerClient
Middleware : You can have a
middleware.ts
at the root of your project or thesrc
folder with matching paths for protected route. If user is not authenticated, redirect to your intended path.middleware.ts
Note :
NEXT_PUBLIC_POCKETBASE_API_URL
, you must have this in your.env
filepocketbase-typegen
by @patmood, you can generate the types using this package and place in@types/pocketbase-types.ts
or a befitting path and update the import paths.I’ve managed to get it working in the new
app
directory, using (https://github.com/vvo/iron-session/issues/560#issuecomment-1324598048 as a base. The only thing to note is that any actions that would update theauthStore
can only happen in the client, fow now.lib/getUserFromCookie.ts
lib/pocketbase.ts
And then, in
app/layout.tsx
I need your NextJS 13 SSR example so much! I’m using NextJS 13 with PocketBase. I’m very confused with how to use PocketBase for user authentication in client components and then fetching user’s data in server components.
In my project, I didn’t get and set cookies (I don’t know how to do that), and I’m not able to get user’s data after user login. It is as if the PocketBase client in the server component that performs data fetching is never aware of the PocketBase client in the client component that performs user authentication.
So what I did for my solution is the following…
So the above helps me on the server and then for the client I have the following.
Thank you for your reply! I tried a little bit and it seems to solve my current problem, which is to get data on the server side after user login. Following Gani’s suggestion, I create a new PocketBase client instance, get the cookie using the
cookies().get('pb_auth')
method in Next.js 13, which returns an object{name: 'pb_auth', value: '{...}'}
, and then convert it to a string, then load the cookie string withauthStore.loadFromCookie
. Now I’m able to read data usingclient.records.getList
. A minimal example:I’m quite new to web development and not very familiar with JS/TS, so maybe there are better ways to write the code.
@bentrynning Are you sure that
authCookie.value
is a raw serialized cookie string and not the token or the json object that contains the token?Note you don’t have to use
pb.authStore.loadFromCookie/exportToCookie
if you are using the Next.js cookies abstraction. To load a token directly into the store you can callpb.authStore.save(token)
.In any case, I’m closing this issue as it is starting to become too off-topic and there isn’t any clear actionable item here.
I’ve tried to check if anything has changed in Next.js couple months ago and unfortunately I wasn’t able to find an easy solution for sharing the SDK AuthStore state between server components and I’m reaching a point where I’d rather not recommend using PocketBase with Next.js as it feels too much work for very little benefit, at least to me.
If someone has a suggestion without having to maintain special Next.js helpers, feel free to open a PR with an example for the README.
This is really amazing work @tsensei! For the middleware.ts,
/((?!api|_next/static|_next/image|favicon.ico|login|phrase|$).*)
, changing^$
->$
excluded the home page for methx @rafifos Your solution works great. The nextjs 13 is a hard one to play with…in comparison SvelteKit is such a better expirence…
I think there is some misunderstanding. The above will work only in a browser context because
document.cookie
is not available in node. If you are making the requests in the browser while only using node for server rendering then that’s OK.But if you want to make requests from the node-server you’ll need to read and set the cookie from the
ServerRequest
andServerResponse
objects (or their equivalent in the new nextjs if you are using nextjs13). When using PocketBase in a server context, you need a unique PocketBase instance on each server request because node is single threaded and requests are usually executed in an event loop, meaning that while you are waiting something to execute, the same process could process another client request and if you use only a single instance the data from the new request may overwrite the state from the initial one. In the browser this is not an issue and you can have a single instance for the entire lifecycle of the application.I understand that modern frameworks blur the line between client and server but please make sure that your code is executed where you expect it to avoid accidentally leaking sensitive information. I still haven’t got the time to explore the new api of nextjs13 and sometime after the v0.8.0 release I’ll try to test it and will add a SSR example for it in the readme.
The default cookie entry should look something like this:
(the expires date should be the the same as the token “exp” claim)
If you have a localStorage entry, this means that you are currently handling the OAuth2 redirect in the browser. Or in other words, the PocketBase instance is running client-side. If you want to have a mixed SDK access (aka. making requests both client-side and server-side) then you’ll have to export the cookie with
exportToCookie({ httpOnly: false })
and change your store to conditionally get and set the cookie either from theServerRequest/ServerResponse
objects or fromdocument.cookie
depending on what environment it is running. Alternatively the browser/client SDK instance could use theauthStore.onChange
listener instead of a custom store:(the above will always update the document.cookie that will be added automatically to every request from the browser to the node-server)
Could you provide a code sample/repo of what you are trying to do?