Open grantsingleton opened 3 months ago
I found the issue goes away when initialized like this:
'use client';
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { useEffect, useRef } from 'react';
interface AblyProviderProps {
children: React.ReactNode;
}
export default function AblyRealtimeProvider({ children }: AblyProviderProps) {
const clientRef = useRef<Ably.Realtime>();
useEffect(() => {
if (!clientRef.current) {
const client = new Ably.Realtime({ key: process.env.NEXT_PUBLIC_ABLY_CLIENT_API_KEY });
clientRef.current = client;
}
}, []);
if (!clientRef.current) return <>{children}</>
return <AblyProvider client={clientRef.current} key='ably'>{children}</AblyProvider>;
}
Of course, as should have been obvious. const client = new Ably.Realtime({ key: process.env.NEXT_PUBLIC_ABLY_CLIENT_API_KEY });
is establishing a connection each time the app re-renders. I found that even the Next JS / Ably example does it this way and must have the same issue with too many connections.
Anyone else using Ably in a Next JS app and knows of a better way to do this than I have done here with the ref?
The problem with this method is the downstream ChannelProviders now throw errors since the ably client does not immediately exist.
Ive also tried this method of initializing outside of the function which seems to be what the react docs suggest:
'use client';
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { useEffect, useRef } from 'react';
interface AblyProviderProps {
children: React.ReactNode;
}
const client = new Ably.Realtime({ key: process.env.NEXT_PUBLIC_ABLY_CLIENT_API_KEY });
export default function AblyRealtimeProvider({ children }: AblyProviderProps) {
return <AblyProvider client={client} key='ably'>{children}</AblyProvider>;
}
but this still has the issue of creating a connection every time you refresh the page. The only solution i've found that maintains one connection is the ref method shown in the previous comment but it isnt usable since it throws errors that brief instant that the client does not exist. Ably is still currently unusable in next js due to this.
Question: can the Ably.Realtime not be a singleton? Why is it creating new connections?
Hey @grantsingleton,
Thanks for bringing this up! Sorry to hear that you're having trouble with the Next.js setup. Improving the developer experience for Next.js is on our roadmap. We'll discuss this internally and come back with recommendations for your use case.
Meanwhile I looked at solution with useRef
that you shared, and problem with ChannelProviders
can be easily fixed. There is no need to initialize Ably client in useEffect
. Ably client constructor is very lightweight and lazy. It's better to rewrite it this way:
export default function AblyRealtimeProvider({ children }: AblyProviderProps) {
const clientRef = useRef<Ably.Realtime>();
if (!clientRef.current) {
const client = new Ably.Realtime({ key: process.env.NEXT_PUBLIC_ABLY_CLIENT_API_KEY });
clientRef.current = client;
}
return <AblyProvider client={clientRef.current}>{children}</AblyProvider>;
}
@ttypic thanks for looking into this. I tried your method and it still allows multiple connections. I got it over 10 on dev. Not sure how. Right now the only method able to hold it to 1 connection is putting it in a useEffect. I'll be trying other methods this week.
I have been using the method with dynamically importing the component and then using the api with token request as shown in the example below and my connection numbers are right.
@ttypic Can the correct details be added to the react/next docs? It would nice if people didn't have to go through all the open issues to find a solution.
I'm having this issue too and can't figure out how to reduce the number of connections. Do we know if this is or what makes this a NextJS-specific issue?
Hi @mikey555 !
After further investigation, we found that the issue is often caused by the hot reloading mechanism (referred to as Fast Refresh in newer Next.js versions). During development, if you update a file containing a new Ably.Realtime
call, the HMR mechanism in your development environment (React, Vite, Next.js apps all include HMR) refreshes the file, creating a new client and opening a new connection. However, the previous client is unaware of this replacement, so the connection count continues to rise with each file edit. This issue is not specific to Next.js but rather applies to any development environment with HMR.
Thus, if you frequently edit a file with a new Ably.Realtime
call during development, the connection count will keep increasing.
While we're working on providing a way for the library to detect when it has been reloaded and needs to close, the following workaround should help:
Simply place your new Ably.Realtime
call in a separate file from those you're constantly updating. For example, extract it into a wrapper component that won't be edited frequently. If that's not feasible, you can manually refresh the page you're working on from time to time to close all open clients and connections.
Hi, I'm also having the same issue, what I did is put Ably in a React hook and using Singleton Pattern may decrease the connections flows.
'use client';
import * as Ably from "ably";
class AblyClient {
private static instance: Ably.Realtime | null = null;
private constructor() {} // Prevent direct instantiation
public static getInstance(): Ably.Realtime | null {
// Ensure this runs only on the client side
if (typeof window === "undefined") {
return null;
}
if (!AblyClient.instance) {
AblyClient.instance = new Ably.Realtime({
authUrl: "/api/ably",
authMethod: "POST",
});
}
return AblyClient.instance;
}
}
export default AblyClient;
In the other components, just call:
const client = AblyClient.getInstance();
Hi @mikey555 !
After further investigation, we found that the issue is often caused by the hot reloading mechanism (referred to as Fast Refresh in newer Next.js versions). During development, if you update a file containing a
new Ably.Realtime
call, the HMR mechanism in your development environment (React, Vite, Next.js apps all include HMR) refreshes the file, creating a new client and opening a new connection. However, the previous client is unaware of this replacement, so the connection count continues to rise with each file edit. This issue is not specific to Next.js but rather applies to any development environment with HMR. Thus, if you frequently edit a file with anew Ably.Realtime
call during development, the connection count will keep increasing.While we're working on providing a way for the library to detect when it has been reloaded and needs to close, the following workaround should help:
Simply place your
new Ably.Realtime
call in a separate file from those you're constantly updating. For example, extract it into a wrapper component that won't be edited frequently. If that's not feasible, you can manually refresh the page you're working on from time to time to close all open clients and connections.
I first initialize ably in my layout.
Then, I set up a channel provider and wrap a route with it.
I then use useChannel in one of the children
Ably then proceeds to establish tons of connections. It's typically 20-40 but got over 250 one time and surpassed my free tier usage just on dev with one user (me).
It's currently unusable due to this. I've looked for what I might be doing wrong but can't figure it out.
Check out these stats of running this on dev. I asked about this on Discord with no response so decided to start an issue. I can't imagine anyone using NextJS is using Ably due to this issue.![Screenshot 2024-06-01 at 6 41 21 AM](https://github.com/ably/ably-js/assets/46718281/6f2d513b-170a-4598-99a0-2ea519687de9)
┆Issue is synchronized with this Jira Task by Unito