如何与 Next.js 一起使用
¥How to use with Next.js
本指南展示了如何在 Next.js 应用中使用 Socket.IO。
¥This guide shows how to use Socket.IO within a Next.js application.
你将无法在 Vercel 上部署你的应用,因为它不支持 WebSocket 连接。
¥You won't be able to deploy your application on Vercel, as it does not support WebSocket connections.
参考:https://vercel.com/guides/do-vercel-serverless-functions-support-websocket-connections
¥Reference: https://vercel.com/guides/do-vercel-serverless-functions-support-websocket-connections
服务器
¥Server
Socket.IO 服务器可以与 Next.js 共享相同的底层 HTTP 服务器。你只需在项目的根目录下创建一个 server.js
文件:
¥The Socket.IO server can share the same underlying HTTP server with Next.js. You just have to create a server.js
file at the root of your project:
import { createServer } from "node:http";
import next from "next";
import { Server } from "socket.io";
const dev = process.env.NODE_ENV !== "production";
const hostname = "localhost";
const port = 3000;
// when using middleware `hostname` and `port` must be provided below
const app = next({ dev, hostname, port });
const handler = app.getRequestHandler();
app.prepare().then(() => {
const httpServer = createServer(handler);
const io = new Server(httpServer);
io.on("connection", (socket) => {
// ...
});
httpServer
.once("error", (err) => {
console.error(err);
process.exit(1);
})
.listen(port, () => {
console.log(`> Ready on http://${hostname}:${port}`);
});
});
server.js
文件成为你的应用的入口点:
¥The server.js
file becomes the entrypoint of your application:
{
"scripts": {
- "dev": "next dev",
+ "dev": "node server.js",
"build": "next build",
- "start": "next start",
+ "start": "NODE_ENV=production node server.js",
"lint": "next lint"
}
}
瞧!
¥And voilà!
参考:https://next.nodejs.cn/docs/pages/building-your-application/configuring/custom-server
¥Reference: https://next.nodejs.cn/docs/pages/building-your-application/configuring/custom-server
这适用于应用路由和页面路由。
¥This works with both the App router and the Pages router.
来自 Next.js 文档:
¥From the Next.js documentation:
在决定使用自定义服务器之前,请记住,仅当 Next.js 的集成路由无法满足你的应用要求时才应使用它。自定义服务器将删除重要的性能优化,例如无服务器功能和 自动静态优化。
¥Before deciding to use a custom server, please keep in mind that it should only be used when the integrated router of Next.js can't meet your app requirements. A custom server will remove important performance optimizations, like serverless functions and Automatic Static Optimization.
无法在 Vercel 上部署自定义服务器。
¥A custom server cannot be deployed on Vercel.
独立输出模式,不跟踪自定义服务器文件,此模式输出一个单独的最小 server.js 文件。
¥Standalone output mode, does not trace custom server files and this mode outputs a separate minimal server.js file instead.
客户端
¥Client
在客户端,我们 React 指南 的所有提示都是有效的。
¥On the client side, all tips from our React guide are valid.
唯一的区别是你需要从服务器端渲染(SSR)中排除 Socket.IO 客户端:
¥The only difference is that you need to exclude the Socket.IO client from server-side rendering (SSR):
- App router
- Pages router
结构:
¥Structure:
├── src
│ ├── app
│ │ └── page.js
│ └── socket.js
└── package.json
"use client";
import { io } from "socket.io-client";
export const socket = io();
"use client"
表示该文件是客户端打包包的一部分,并且不会进行服务器渲染。
¥"use client"
indicates that the file is part of the client bundle, and won't be server-rendered.
参考:https://next.nodejs.cn/docs/app/building-your-application/rendering/client-components
¥Reference: https://next.nodejs.cn/docs/app/building-your-application/rendering/client-components
"use client";
import { useEffect, useState } from "react";
import { socket } from "../socket";
export default function Home() {
const [isConnected, setIsConnected] = useState(false);
const [transport, setTransport] = useState("N/A");
useEffect(() => {
if (socket.connected) {
onConnect();
}
function onConnect() {
setIsConnected(true);
setTransport(socket.io.engine.transport.name);
socket.io.engine.on("upgrade", (transport) => {
setTransport(transport.name);
});
}
function onDisconnect() {
setIsConnected(false);
setTransport("N/A");
}
socket.on("connect", onConnect);
socket.on("disconnect", onDisconnect);
return () => {
socket.off("connect", onConnect);
socket.off("disconnect", onDisconnect);
};
}, []);
return (
<div>
<p>Status: { isConnected ? "connected" : "disconnected" }</p>
<p>Transport: { transport }</p>
</div>
);
}
结构:
¥Structure:
├── src
│ ├── pages
│ │ └── index.js
│ └── socket.js
└── package.json
import { io } from "socket.io-client";
const isBrowser = typeof window !== "undefined";
export const socket = isBrowser ? io() : {};
isBrowser
检查很重要,因为它可以防止 Next.js 在进行服务器端渲染时尝试创建 Socket.IO 客户端。
¥The isBrowser
check is important, as it prevents Next.js from trying to create a Socket.IO client when doing server-side rendering.
参考:https://next.nodejs.cn/docs/pages/building-your-application/rendering/client-side-rendering
¥Reference: https://next.nodejs.cn/docs/pages/building-your-application/rendering/client-side-rendering
import { useEffect, useState } from "react";
import { socket } from "../socket";
export default function Home() {
const [isConnected, setIsConnected] = useState(false);
const [transport, setTransport] = useState("N/A");
useEffect(() => {
if (socket.connected) {
onConnect();
}
function onConnect() {
setIsConnected(true);
setTransport(socket.io.engine.transport.name);
socket.io.engine.on("upgrade", (transport) => {
setTransport(transport.name);
});
}
function onDisconnect() {
setIsConnected(false);
setTransport("N/A");
}
socket.on("connect", onConnect);
socket.on("disconnect", onDisconnect);
return () => {
socket.off("connect", onConnect);
socket.off("disconnect", onDisconnect);
};
}, []);
return (
<div>
<p>Status: { isConnected ? "connected" : "disconnected" }</p>
<p>Transport: { transport }</p>
</div>
);
}
我们本可以使用:
¥We could have used:
const [isConnected, setIsConnected] = useState(socket.connected);
代替:
¥instead of:
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
if (socket.connected) {
onConnect();
}
// ...
});
但这会触发 Next.js 编译器触发的一些警告,因为客户端渲染的页面可能与服务器渲染的输出不匹配。
¥but this triggers some warnings from the Next.js compiler, as the client-rendered page may not match the server-rendered output.
未捕获的错误:文本内容与服务器渲染的 HTML 不匹配。
¥Uncaught Error: Text content does not match server-rendered HTML.
在上面的示例中,transport
变量是用于建立 Socket.IO 连接的底层传输,它可以是:
¥In the example above, the transport
variable is the low-level transport used to establish the Socket.IO connection, which can be either:
HTTP 长轮询 (
"polling"
)¥HTTP long-polling (
"polling"
)WebSocket(
"websocket"
)WebTransport(
"webtransport"
)
如果一切顺利,你应该看到:
¥If everything went well, you should see:
Status: connected
Transport: websocket
然后,你可以在 Socket.IO 服务器和客户端之间交换消息:
¥You can then exchange messages between the Socket.IO server and client with:
socket.emit()
发送消息¥
socket.emit()
to send messages
socket.emit("hello", "world");
socket.on()
接收消息¥
socket.on()
to receive messages
socket.on("hello", (value) => {
// ...
});
以上就是全部内容了,感谢大家的阅读!
¥That's all folks, thanks for reading!