Recently, I often needed to share bookmarks with teammates, things like code repositories, documentation links, or UI designs on Figma. That’s when an idea came to me: What if I could organize those bookmarks into a dashboard and generate a public webpage to share everything at once?
If I could make this happen, I'd be able to prepare a dashboard by one or more bookmark collections and then simply generate a corresponding webpage. Instead of copying and pasting bookmarks manually, I could just send the URL to my teammates, and they’d be able to open the URL and view all the resources on a clean, static dashboard. So convenient!
Excited by the idea, I jumped right into building it. Since the Bookmark Dashboard(a browser extension I previously developed) itself was already built, what I mainly needed to do is adding a “Generate Sharing Link” button. Clicking it would save the current dashboard layout and produce a unique URL, anyone opening it would see an exact replica of that dashboard at that moment.
But as it turns out, updating UI is the easy part, the real work happens on the backend to handle what occurs when the button is clicked. After some consideration, I decided to go with the simplest and fastest way to get this working.
Below, I’ll walk through how I implemented the backend using Next.js and MongoDB.
Implementation Approach
The dashboard shows the same content as long as the layout passed in is identical. That means the sharing feature comes down to two steps: save the layout when generating the sharing link, and restore it when the link is opened.
So The backend implementation is quite straightforward:
- Create an API to receive the dashboard layout and store it in the database.
- Create a page that retrieves the stored layout and renders it when visited. The URL of this page will become the sharing link.
Dashboard Link API
My backend service is built using Next.js App Router. To define an API endpoint, I just need to follow the folder-based routing convention.
Since I wanted the API path to be /dashboard/link, I created the folder structure accordingly, and placed a route.js inside.
src/app/dashboard
└── link
└── route.js
Inside 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) {
const accout = ... // read current user's account
const { content } = await req.json(); // read parameter - the dashboard layout
// connect mongodb and save
await connectMongo();
const item = await LinkData.findOneAndUpdate({ account }, { content }, { upsert: true, new: true });
return NextResponse.json(
{ re: item._id },
{ status: 200 }
);
}
The PUT function automatically handles incoming PUT requests to /dashboard/link. After connecting to MongoDB, it saves the content (dashboard layout) under the user’s account.
Now the frontend (the 'Generate' button's click handler) can call this API to save the dashboard layout. The returning _id will be used to construct the sharing link.
Dashboard Page
To specify the dashboard by its _id, the sharing link should be something like /dashboard/link/[id]. I created a page.js under app/dashboard/link/[id].
src/app/dashboard
└── link
├── [id]
│ ├── Dashboard.js
│ └── page.js
└── route.js
Here’s page.js:
// database util
import connectMongo from '@/db/mongo';
// database model for dashboard layout data
import LinkData from '@/db/entity/link_data';
// the component
import Dashboard from './Dashboard';
async function loadData(id) {
await connectMongo();
let data = null;
if (id) {
data = await LinkData.findById(id).exec();
}
return data;
}
export default async function DashboardLinkPage({ params }) {
const id = params.id;
const data = await loadData(id);
let layout;
if (data?.content) {
const content = JSON.parse(data.content);
layout = content?.layout;
}
return <Dashboard layout={layout} />;
}
This is a server-side page (notice there’s no "use client" directive).
It extracts the id from the route params, uses it to fetch the stored layout via loadData, and passes the data to the Dashboard component (built with React-Grid-Layout, as I introduced in an earlier post).
Simply replace the [id] in /dashboard/link/[id] with the actual _id value. That becomes the sharing link that can be sent to others. When they open it, they'll see a static dashboard that looks exactly the same as the one used to generate the link (the _id). That's because the _id lets the system retrieve the exact layout that was saved earlier.
A Note on MongoDB Connection
Both route.js and page.js use the same connectMongo helper:
import mongoose from 'mongoose';
// reuse the connection across concurrent requests and hot reloads (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;
}
try {
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;
}
Since both route.js and page.js run on the server side, they can share the same connection logic and benefit from connection reuse.
This pattern of querying the database directly from server-side pages is one of Next.js’s standout features. It enables server-side data fetching and rendering, which differs significantly from client-side frameworks like React or Vue.
Wrapping Up
So the backend is all set. Here is how the flow works:
To generate a dashboard sharing link, the frontend (from the 'Generate' button's click handler) calls the /dashboard/link API. After saving the layout, the API returns an _id, which will be used to construct the sharing link /dashboard/link/_id. Opening the link tells the page to grab that _id from the path, fetch the saved layout, and render the dashboard content.
This sharable dashboard feature is now live in the latest version of Bookmark Dashboard. It’s already saving me a lot of time when sharing resources with others. It's free, and hope it can help more people share their collections quickly and easily.
Thanks for reading!



Top comments (0)