DevTab: Rebuilding Hackertab as a Private Firefox New Tab Extension
I have a strange pattern with browser extensions. I do not like installing too many of them, but I also keep finding small cases where I want the browser to behave in a way that is slightly more useful for me.
This happened before with my first Firefox extension, ImgBlock. I wanted something simple, local, understandable, and not one acquisition away from becoming adware. This time the itch was my new tab page.
I usually want my new tab to show useful developer news. Not a dashboard for my entire life, not a productivity religion, not a motivational quote with a blurred mountain in the background. Just a few useful sources that I already check anyway.
I found Hackertab.dev, and it was close to what I wanted. It had the right general idea: a developer news page with cards for sources like GitHub, Hacker News, and others.
But it was also a general public product. That means it had things I do not need, like server connections, analytics, ads, donations, authentication/accounts, external backend calls, multiple browser targets, and a larger collection of sources than I personally care about.
So I forked it and turned it into DevTab.
The goal was not to build a competing product. It was not even to make a polished public extension. The goal was much smaller: make a Firefox new tab extension that I can understand, build, sign, install, and maybe share with a couple of friends.
The commit history tells the story quite well. I started from something useful, then removed the parts that violated my privacy, add features that I don't want or like.
The first large pass was mostly subtraction. In the initial cleanup and refactor commit, I removed Amplitude, Sentry, Firebase, advertising components, donation prompts, auth flows, user account code, feature flags, remote configuration fetching, changelog code, RSS support, Product Hunt, Dev.to, Medium, Hashnode, FreeCodeCamp, Indie Hackers, Hackernoon, conference feeds, and other parts that made sense for Hackertab as a public product but not for my personal use.
This is the kind of refactor I like. It is not adding a clever abstraction. It is deleting everything that makes the project harder to reason about. Also these were privacy nightmare, and I hate ads.
What remained is much smaller in scope. DevTab has four sources: GitHub, Hacker News, Lobsters, and Reddit. That is enough for me. GitHub repos are fetched through the GitHub Search API. Hacker News comes from the Hacker News Firebase API. Lobsters uses its public JSON endpoint, for example hottest.json. Reddit uses public JSON endpoints from selected subreddits.
This also changed the data model of the project. In the original version, more of the fetching relied on Hackertab's own backend and product infrastructure. In DevTab, the extension talks directly to the public APIs it needs. There is no central news backend owned by me, no account system, and no remote service that has to stay alive for the extension to be useful.
Of course, this created one browser-extension problem immediately: CORS.
Some APIs work fine from the extension page, and some do not expose the headers needed for a normal web fetch. Lobsters and Reddit are the annoying cases here. So I added a small background-script proxy and a wrapper called extensionFetch. When the code detects that it is running inside an extension context, it sends the request to the background script. The background script then performs the request using the extension host permissions and returns the response.
It is a small piece of code, but it is one of the important parts of the project. It keeps the application code normal enough to read while still respecting how Firefox extensions actually work.
I also moved the extension toward being Firefox-only. Hackertab had support for Chrome and Firefox, with merged manifest files and separate build scripts. That is useful for this kind of project.
It is also more surface area than I need. DevTab has a single public/manifest.json with the Firefox metadata I care about.
The new manifest made this explicit. The extension became Manifest V3, declares storage,
and uses host permissions only for the services DevTab talks to: GitHub, Hacker News, Lobsters, and Reddit. The AMO data collection declaration is also explicit: none. The extension has its own ID, its own homepage, and is configured as a new tab override.
This is also why I submitted it to Firefox Add-ons as a private or self-distributed listing. I am not trying to publish it as a public extension-store product.
I just want Mozilla to sign it properly so I can install it on my normal Firefox profiles without temporary-extension gymnastics, and so I can distribute it to a couple of friends in a normal way. Mozilla's own signing and distribution overview explains the basic reason: add-ons need to be signed before they can be installed in release and beta versions of Firefox. Their self-distribution documentation is the model that fits this project better than a public AMO listing.
I also added the usual thing I add to almost every project now: a Makefile.
There is nothing glamorous here. It has targets for installing dependencies, running the dev server, type-checking, building, packaging the Firefox extension, creating a source archive for review, cleaning artifacts, and making a release. The point is the same as always: I do not want to remember the exact command in three months. I want to type this and get a signed-extension-ready archive:
make package
The release target also bumps both public/manifest.json and package.json, then builds both the extension archive and the source archive. That matters for AMO review because the reviewer needs to see the source used to build the submitted extension. It is a small procedural thing, but those small procedural things are where extension projects become annoying if they are not automated.
I also switched the project fully to npm and removed yarn.lock. This was partly cleanup and partly reducing another source of ambiguity. One package manager is enough. The project now uses package-lock.json, and the README tells the same story as the build system:
npm install
make package
Later, I updated the Node target to Node.js 22 and upgraded the dependency set. That required small compatibility fixes, especially around TanStack Query and React Router. This is one of those changes that is boring in the best possible way. It makes the project sit on current tooling instead of carrying a half-old JavaScript stack forever.
The rest of the work was mostly making the fork feel like its own thing. I renamed Hackertab references to DevTab, changed the local storage key, changed the repository links, changed the logo text, updated the onboarding text, and removed the old public-product language from the README. This was not only cosmetic. It was a way to make the repository honest about what it had become: not Hackertab with a few preferences changed, but a personal Firefox extension derived from Hackertab.
I also removed referral parameters from outgoing links. This was one of the small details I cared about. When I click a link from my new tab page, I do not need ref=hackertab.dev attached to it. I am not running a growth funnel. I am opening a link.
The remote configuration was replaced with a bundled tag list. I do not need a backend to tell my own extension which topics exist. The list is now local and includes languages, AI and ML topics, backend topics, databases, infrastructure, web development, embedded systems, security, and a few general topics. This makes the extension more boring, which in this case is a feature. After installation, it has the configuration it needs.
The Reddit logic also became more practical for my use case. If tags are selected, DevTab maps them into subreddit names and fetches from those subreddits. If no tags are selected, it falls back to a few general technical subreddits like programming, technology, and compsci. Results are merged, deduplicated, sorted by score, and trimmed.
GitHub is handled in a similar practical spirit. The selected tags are converted into language filters when possible, and the query searches recently created repositories sorted by stars. It is not an exact clone of GitHub Trending, but it is close enough for what I want from a new tab page: a fast glance at interesting projects.
The interesting thing about this project is that most of the value came from removing things, not adding them. The original Hackertab.dev is trying to be useful to many people, across browsers, with a product model, user accounts, analytics, ads, a backend, and many content sources. DevTab is trying to be useful to me.
That changes the engineering choices.
I do not need authentication, so the auth system goes away. I do not need analytics, so the analytics library goes away. I do not need ads, so the ad system goes away. I do not need Chrome support, so the Chrome build path goes away. I do not need a public web version, so the web manifest and web-specific packaging go away. I do not need twelve news sources, so most cards go away.
What remains is easier to audit. It is easier to build. It is easier to explain. It has fewer reasons to contact anything other than the sources it displays.
There is also something personally satisfying about taking a project that almost fits and making it fit exactly. Forking is not only about adding features. Sometimes it is about removing the assumptions that came from a different use case.
DevTab is now a small Firefox new tab extension that shows developer news from four sources, uses no tracking, has no ads, does not need an external backend, and is signed through AMO private or self-distribution for personal use. That is a very narrow scope, but it is the scope I wanted.
The source is public on GitHub, but I am treating it as a personal extension first. If someone else finds it useful, that is fine. But the main user is still me, and that makes the design decisions much easier.