ably-js icon indicating copy to clipboard operation
ably-js copied to clipboard

AblyProvider and Next - Ably connects from server even with use client

Open bookercodes opened this issue 10 months ago • 3 comments

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).

┆Issue is synchronized with this Jira Task by Unito

bookercodes avatar Apr 17 '24 09:04 bookercodes

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>

    </>
}

bookercodes avatar Apr 17 '24 09:04 bookercodes

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.

VeskeR avatar Apr 17 '24 13:04 VeskeR

Ah, interesting!

You're right, something like this works well:

const client = new Ably.Realtime({ authUrl: '/api/ably', autoConnect: typeof window !== 'undefined' })

bookercodes avatar Apr 19 '24 09:04 bookercodes