Astro Integration to Source Chrome Bookmarks


I’ve seen a few people recently on Twitter mention the idea of hosting/storing bookmarks and links their websites. For whatever reason - it hit home with me and sounded like an awesome idea!

I wanted to make it as easy as possible. Naturally, JSON sounds like a great way to organize them for my implementation. That said, I didn’t really want to build a new process around maintaining a JSON file manually. I enjoy the bookmarking experience in Chrome, even though I would say I’m picky about what I actually bookmark.

So - I found the button in Google Chrome that allows you to export your bookmarks via chrome://bookmarks/


This generates an HTML file of the format <!DOCTYPE NETSCAPE-Bookmark-file-1> that lists your bookmarks by folder with: URL, Added Date, Favicon, and of course Title. Naturally, you can see how all of this would be useful for generating a JSON file to reference in an Astro page/component.

I copied that file into a local directory /src/content/bookmarks and setup the JavaScript below to build a bookmarks.json at build time. There are a few known caveats:

  • I added the .html to .gitignore so my personal bookmarks aren’t leaked to GitHub
  • I plan on building/generating this locally (right now)
  • I plan on deleting bookmarks.json and copying in a new bookmarks.html whenever I want to update the list.
  • I’m curating these links under a Published Bookmarks folder.
import fs from 'fs';
import path from 'path';
import { JSDOM } from 'jsdom';

function parseBookmarksHtml(html) {
    const dom = new JSDOM(html);
    const doc = dom.window.document;
    const publishedSection = Array.from(doc.querySelectorAll('dt > h3')).find(h3 => h3.textContent.includes('Published Bookmarks'));
    const links = publishedSection ? publishedSection.nextElementSibling.querySelectorAll('a') : [];
    const bookmarks = Array.from(links).map(link => ({
        title: link.textContent.trim(),
        href: link.getAttribute('href'),
        add_date: new Date(parseInt(link.getAttribute('add_date')) * 1000).toISOString(),
        icon: link.getAttribute('icon')
    return bookmarks;

function loadAndConvertHtmlFile(filePath) {
    try {
        const htmlContent = fs.readFileSync(filePath, 'utf8');
        return parseBookmarksHtml(htmlContent);
    } catch (error) {
        throw new Error(`Failed to load or parse HTML file: ${filePath}`);

async function processBookmarksFolder(folderPath) {
    const allBookmarks = [];  // Array to store all bookmarks

    const files = fs.readdirSync(folderPath);
    for (const file of files) {
        if (path.extname(file).toLowerCase() === '.html') {
            const filePath = path.join(folderPath, file);
            try {
                const bookmarks = loadAndConvertHtmlFile(filePath);
                allBookmarks.push(...bookmarks);  // Add bookmarks to the array
            } catch (error) {
                console.log('Error processing file:', file, error);

    // Only write to bookmarks.json if allBookmarks is not empty
    if (allBookmarks.length > 0) {
        const jsonFilePath = path.join(folderPath, 'bookmarks.json');
        fs.writeFileSync(jsonFilePath, JSON.stringify(allBookmarks, null, 2));
        console.log('All bookmarks saved to:', jsonFilePath);
    } else {
        console.log('No bookmarks found. bookmarks.json was not overwritten.');

// Astro integration
export function generateBookmarks({ bookmarksPath = './src/content/bookmarks' }) {
    return {
        name: "generate-bookmarks",
        hooks: {
            "astro:build:setup": async ({ logger }) => {
                try {
                    await processBookmarksFolder(bookmarksPath);
                    logger.info("Bookmark generation completed.");
                } catch (e) {
                    logger.error("Error in Bookmark generation:", e);

All of this could be iterated on - and probably done better. This works for me right now - and is a great starting place to hosting a Links page on my website. If you have suggestions or ideas - feel free to @ me on X/Twitter 🙂