ably-js
ably-js copied to clipboard
AblyProvider and Next - Ably connects from server even with use client
Consider the following client component in Next 14.1 with the app router:
'use client'
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react'
import Link from 'next/link'
import { UserButton } from "@clerk/nextjs";
export default function ChatLayout = function ({ children }) {
const client = new Ably.Realtime({ authUrl: '/api/ably' })
return <>
<nav>
<h1>Comet</h1>
<UserButton showName={true} />
</nav>
<aside>
<ul>
<li>
<Link href="/chat/announcements">#Announcements</Link>
</li>
<li>
<Link href="/chat/general">#General</Link>
</li>
<li>
<Link href="/chat/random">#Random</Link>
</li>
<li>
<Link href="/chat/mods-only">#Mods-only</Link>
</li>
</ul>
</aside>
<AblyProvider client={client}>
{children}
</AblyProvider>
</>
}
Even though it's a client component, Next still runs it on the server as part of a pre-fetch, causing AblyProvider
to connect form the server.
In my case, that causes an error explained here https://github.com/ably-labs/react-hooks/issues/8
10:30:09.593 Ably: Auth.requestToken(): token request signing call returned error; err = Error: `input` must not start with a slash when using `prefixUrl`
So, my server never actually connects (I just get a noisy error), but it's totally plausible the sever connects if you use an absolute URL or another auth implementation, causing a surprising duplicate connection.
What is the implication of this? Well, the error in the console is a bit verbose and it can look messy in the Ably developer console when the client appears to connect twice, but the implication is probably quite little, isn't that right? I thought it could result in duplicate connections (for example, in an avatar stack), but usePressence
, etc. is run as an effect (client-side only).
For the benefit of anyone else with this issue, I am including my solution below, which is to turn ssr
off for the component.
Team Ably, I'm not a React or Next expert or anything, so is this the right way to go about it?
Is there a way to make the AuthProvider
work more friendly with Next SSR? Maybe if it's running in a server context, it shouldn't try and connect? I'm not sure, as there may be implications for testing etc.
Any advice on best practices appreciated!
'use client'
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react'
import Link from 'next/link'
import { UserButton } from "@clerk/nextjs";
export default dynamic(() => Promise.resolve(ChatLayout), {
ssr: false
})
const ChatLayout = ({ children }) => {
const client = new Ably.Realtime({ authUrl: '/api/ably' })
return <>
<nav>
<h1>Comet</h1>
<UserButton showName={true} />
</nav>
<aside>
<ul>
<li>
<Link href="/chat/announcements">#Announcements</Link>
</li>
<li>
<Link href="/chat/general">#General</Link>
</li>
<li>
<Link href="/chat/random">#Random</Link>
</li>
<li>
<Link href="/chat/mods-only">#Mods-only</Link>
</li>
</ul>
</aside>
<AblyProvider client={client}>
{children}
</AblyProvider>
</>
}
Hi @bookercodes ! Thank you for bringing this up and finding other existing solutions.
Yes, the approach described here: https://github.com/ably-labs/react-hooks/issues/8#issuecomment-1697410212, is currently the suggested one: use next/dynamic
with the ssr: false
option in order to force components to load on the client side.
So your solution looks right:
export default dynamic(() => Promise.resolve(ChatLayout), {
ssr: false
})
This has also prompted us to discuss how we can improve the usage of ably-js in Next.js with SSR to avoid issues like this, and we will investigate this further. There might be a better solution (instead of disabling SSR altogether). For example, if you detect that you are running on the server during SSR, then you can set the autoConnect: false
option when creating the Ably.Realtime
instance to prevent it from trying to connect to Ably servers. This should be the desired behavior during SSR; we don't want to perform any real operations there.
We will investigate this further and update this issue with more information.
Ah, interesting!
You're right, something like this works well:
const client = new Ably.Realtime({ authUrl: '/api/ably', autoConnect: typeof window !== 'undefined' })