使 Bookmark Dashboard 可在线共享
Source: Dev.to
介绍
最近,我经常需要与团队成员共享书签——代码仓库、文档链接、Figma 上的 UI 设计等。于是灵感来了:
如果我能把这些书签组织成一个仪表盘,并生成一个公共网页一次性分享所有内容会怎样?
如果能够实现,我就可以从一个或多个书签集合中准备一个仪表盘,然后直接生成相应的网页。无需手动复制粘贴书签,只需把 URL 发给同事,他们就能看到一个整洁的静态仪表盘,展示所有资源。如此便利!
带着这个想法,我立刻开始动手。由于我之前已经开发了 Bookmark Dashboard(一个浏览器扩展),主要任务就是添加一个 “生成共享链接” 按钮。点击后会:
- 保存当前仪表盘的布局。
- 生成一个唯一的 URL,打开后即可呈现当时仪表盘的完整复制。

更新 UI 是比较容易的工作;真正的挑战在于点击按钮时后端的处理。经过一番考虑,我选择了最简洁、最快速的实现方式。
下面,我将详细说明如何使用 Next.js 和 MongoDB 实现后端功能。
实现方法
只要传入的布局相同,仪表盘就会显示相同的内容。因此,共享功能归结为两个步骤:
- 保存 布局,在生成共享链接时记录。
- 恢复 布局,在打开链接时重新加载。
后端实现相对直接:
- 创建一个 API 来接收仪表盘布局并将其存入数据库。
- 创建一个页面,在访问时读取已存储的布局并渲染。
该页面的 URL 即为共享链接。
仪表盘链接 API
我的后端服务使用 Next.js App Router 构建。要定义一个 API 端点,只需遵循基于文件夹的路由约定。
因为我希望 API 路径为 /dashboard/link,所以创建了如下文件夹结构,并在其中放置了 route.js:
src/app/dashboard
└── link
└── route.js
route.js
import { NextResponse } from 'next/server';
// database util
import connectMongo from '@/db/mongo';
// database model for dashboard layout data
import LinkData from '@/db/entity/link_data';
export async function PUT(req) {
// TODO: read current user's account (e.g., from auth token)
const account = /* ... */;
// Read the dashboard layout from the request body
const { content } = await req.json();
// Connect to MongoDB and upsert the layout
await connectMongo();
const item = await LinkData.findOneAndUpdate(
{ account },
{ content },
{ upsert: true, new: true }
);
// Return the generated document ID
return NextResponse.json(
{ re: item._id },
{ status: 200 }
);
}
PUT 函数会自动处理指向 /dashboard/link 的 PUT 请求。连接到 MongoDB 后,它会将布局(content)保存到对应用户的账户下。前端(“Generate” 按钮的点击处理函数)可以调用此 API,返回的 _id 用于构建分享链接。
仪表盘页面
要通过其 _id 引用仪表盘,分享链接应为 /dashboard/link/[id]。我在 app/dashboard/link/[id] 下创建了一个 page.js:
src/app/dashboard
└── link
├── [id]
│ ├── Dashboard.js
│ └── page.js
└── route.js
page.js
// database util
import connectMongo from '@/db/mongo';
// database model for dashboard layout data
import LinkData from '@/db/entity/link_data';
// the component that actually renders the dashboard
import Dashboard from './Dashboard';
async function loadData(id) {
await connectMongo();
if (!id) return null;
return await LinkData.findById(id).exec();
}
export default async function DashboardLinkPage({ params }) {
const { id } = params;
const data = await loadData(id);
let layout = undefined;
if (data?.content) {
try {
const parsed = JSON.parse(data.content);
layout = parsed?.layout;
} catch (e) {
console.error('Failed to parse layout JSON', e);
}
}
// This is a server‑side component (no "use client" directive)
return <Dashboard layout={layout} />;
}
该页面从路由参数中提取 id,从 MongoDB 获取存储的布局,解析后将其传递给 Dashboard 组件(使用 React‑Grid‑Layout 构建,详见之前的文章)。
将 /dashboard/link/[id] 中的 [id] 替换为 API 返回的实际 _id 值。该 URL 即为可发送给他人的分享链接。对方打开后,将看到一个与生成链接时完全相同的静态仪表盘。
关于 MongoDB 连接的说明
route.js 和 page.js 都使用相同的 connectMongo 辅助函数,以在并发请求和热重载期间复用单个连接:
import mongoose from 'mongoose';
// Reuse the connection across requests (module re‑imports)
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function connectMongo() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const uri = process.env.MONGODB_URI;
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
cached.promise = mongoose.connect(uri, opts).then((mongoose) => mongoose);
}
cached.conn = await cached.promise;
return cached.conn;
}
export default connectMongo;
这种模式确保 MongoDB 客户端在每个服务器实例中仅实例化一次,从而提升性能并防止连接耗尽错误。
结论
只需几行后端代码,Bookmark Dashboard 现在就支持生成可分享的静态仪表盘页面。工作流程如下:
- 点击 Generate Sharing Link → UI 调用
PUT /dashboard/link接口。 - API 存储布局并返回一个
_id。 - 构建 URL
/dashboard/link/并分享它。 - 任何访问该 URL 的人都会看到完全相同的仪表盘布局,且在服务器端渲染。
if (!cached.promise) {
cached.promise = mongoose
.connect(MONGO_URI, {
serverApi: { version: '1', strict: true, deprecationErrors: true },
})
.then((mongoose) => {
return mongoose;
});
}
cached.conn = await cached.promise;
await mongoose.connection.db.admin().command({ ping: 1 });
console.log('Successfully connected to db!');
} catch (e) {
cached.promise = null;
throw e;
}
return cached.conn;
}
全屏控制
- 进入全屏模式
- 退出全屏模式
由于 route.js 和 page.js 都在服务器端运行,它们可以共享相同的连接逻辑并受益于连接复用。
这种直接在服务器端页面查询数据库的模式是 Next.js 的突出特性之一。它实现了服务器端数据获取和渲染,这与 React 或 Vue 等客户端框架有显著区别。
完成总结
后端已经全部就绪。以下是整个流程的工作方式:
- 生成仪表盘分享链接 – 前端(在 “Generate” 按钮的点击处理函数中)调用
/dashboard/link接口。 - 保存布局 – 接口存储布局并返回一个
_id。 - 构建分享链接 – 使用该
_id构造链接/dashboard/link/_id。 - 打开链接 – 页面从路径中提取
_id,获取已保存的布局,并渲染仪表盘内容。
示例

打开分享链接

此可分享的仪表盘功能已在最新版本的 Bookmark Dashboard 中上线。它已经在我与他人共享资源时为我节省了大量时间。该功能免费,希望能帮助更多人快速、轻松地分享他们的收藏。
感谢阅读!