1,689 words
~8min read

Launching is the Second Hardest Part

  May 19th 2025

If Starting is the Hardest Part then surely launching is the second hardest part. And I don’t mean it’s not as hard as starting, it’s just the second time you’ll be facing the hardest part of getting a product off the ground. Just like the first 80% is the hardest, the second 80% of the work is what drags, and the third 80% is really what gets you…

But it’s a big moment when you get there!

Cat Search Site

Cat Search beta is finally alive and working!

But wow… it took longer that I thought to get there even for such a small project so let me talk you through some of the challenges.

The Known#

Starting with all the stuff I knew about above my MVP release line, there were lots of small details to wrap up like setting up backend monitoring (I went with Honeycomb) and reporting any errors from the frontend through a new API so I can see if there are any problems affecting the users. There were also last minute bugs to fix like the one where I discovered the Google tablet markup is significantly different than the desktop layout and broke all my selectors.

Then there was getting the email verification set up and working so my API wouldn’t be wide open to abuse. I went with resend and got stuck on verifying the domain for 2 days waiting for DNS to propagate before realizing there was a copy pasta mistake…

And of course I needed to make a website which is like starting a whole new project in itself with all the decisions to make on tech stacks all over again. I stuck with the familiar though used a new site template which takes some time to learn. But to make a product website you need to first do things like choose fonts, colors, and a logo, which all require you to think about the nebulous concept that is brand identity.

Coming up with a logo was challenging in itself to find something representative. I spent hours looking for inspiration and then trying some AI tools (I promise I’ll pay an artist to make a better version if this thing ever makes money) before coming up with something half way decent. Then I spent many more hours more converting it to SVG, cleaning up the paths, optimizing it so it’s not a 2MB SVG, and then creating various downsized versions. Even as I’m writing this now and looking at the site I’m getting sidetracked looking at the favicon version and going to tweak it.

It goes without saying that in order for anyone else to see what you’ve made you actually have to deploy it somewhere. And in order to not cause more work for yourself when you change things you need to get build and deploy automation set up. My first approach was to build it alongside the API and have ASP.NET serve it up as static files just so I’d only have one build artifact and thing to deploy. I got that all set up and working but there were some issues with the DNS setup where Route66 requires you to use a CNAME record which doesn’t allow you to omit the “www” subdomain and also messes with the email DNS setup. After messing with that a bit I just pivoted to using the same setup as this site and host the API separately so I can use an A record.

The Unknown#

After doing all that different stuff I was feeling pretty good and ready to tackle the unknown part of publishing to the Chrome and Firefox extension “web stores” which I’d never done before. I had done a similar “submit lots of different promotional images in very specific resolutions” dance when publishing Macrocosm to the app store but it’s just one more step that eats time.

Chrome extension image requirements

After you submit the extension you have to wait a day or two for some review process to approve it and let the extension be published and visible to download. Seeing what you’ve made alongside all the other extensions is a good feeling, but that feeling was taken away when I installed it and everything just… failed

Almost as disparaging as the fact that local development is working fine and prod is completely busted is the fact that I’m also getting different errors between Chrome and Firefox.

The first problem to tackle was that Chrome was throwing errors that the methods off the browser object exposed to extensions were undefined. It was news to me that Chrome doesn’t support that because the framework I’m using to develop the extension seemingly automatically polyfills it during local development but requires a command line argument to build with the polyfill included. Sigh…

The next (and biggest) problem was all my API calls were failing with CSP errors. To make matters that much more challenging to debug, both Google and DuckDuckGo are throwing their own CSP errors like

Content-Security-Policy: The page’s settings blocked an event handler (script-src-attr) from being executed because it violates the following directive: “script-src 'nonce--zpqhE9NZPHdP41-FRX-Xw' 'strict-dynamic' 'report-sample' 'unsafe-eval' 'unsafe-inline' https: http:” Source: _rtf(this)

on Google, and this one on DDG:

Content-Security-Policy: The page’s settings blocked the loading of a resource (default-src) at https://duckduckgo.com/dist/wpm.1619.ea48354a2359f567744b.js because it violates the following directive: “default-src 'none'”

After confirming the issue was different in prod vs. local because I was hitting localhost for my API while developing it which is apparently exempt from CSP. As I started researching ways to get around this the sense of dread crept in that this would be a total show stopper and all my work would be for naught. Finding out what is and isn’t allowed with CSP for extensions is pretty challenging, and the MDN documentation is even contradictory on the matter which adds to the fun. After a few dead ends messing with the permissions and host_permissions in the extension manifest I found that the actual way around it is to set up a background script, then use browser.runtime.sendMessage and browser.runtime.onMessage.addListener to make the API calls in the background script and pass the data back and forth.

That didn’t sound so bad at first other than a bunch more boilerplate to strongly type the parameters and return types but it turns out this is a super annoying function to work with because the responses automatically get wrapped in a promise so you can’t use async/await inside of it. I need to await getting the user credentials out of extension storage before making the API call and then also pass errors back to the extension which the signup flow relies on. I feel dumb saying this but it took me 6 or 7 tries publishing new versions to actually get this working right. I kept thinking it was all working only to discover something was broken in the other browser or another edge case of error handling didn’t work. This was extremely demotivating to feel suck going back and forth so many times with this one function that I feel like shouldn’t even need to exist. How exactly does it add security to make your API calls in a background script instead of directly from the content script? Why wouldn’t an allow list of domains the extension can talk to be sufficient?

The last major problem was all the custom fonts I included fail to load in the prod build, once again due to CSP restrictions. There’s limited info on this problem as well but the only reasonable solution seems to be to inline the fonts as base64 data. Another rabbit hole to step into, yay. I try a whole bunch of different things but none work and the best I can do is open an issue and give up on custom fonts for now.

Another kicker to debugging all these problems is that browser inspectors are of course different between Chrome and Firefox, you can’t install arbitrary extensions in Firefox and instead need the separate Firefox Developer Edition installed, and in Chrome there doesn’t seem to be any way to get the full inspector open for a background script so you’re left trying to interpret crappy browser error messages like Error: Could not establish connection. Receiving end does not exist. I also ran into issues where the exact same build behaved differently between Firefox Developer Edition installed temporarily and the published version installed in my normal Firefox. But going into the debugging menu and opening the inspector somehow fixed it!?! There was also a time where after using a newly installed version for 10 minutes or so in Firefox it stopped working and I discover it somehow spontaneously uninstalled the extension!?!?!? ARRRRGH

It’s a bad vibe to feel like you’re building on shifting sand and I don’t yet know how I can add integration or end to end tests that would give me confidence in catching all the differences between browsers and build artifacts.

The Takeaway#

If it was easy everyone would be doing it I suppose. So persistence is key, even when life also “gets in the way”, or, to put it better, just takes precedence over a side project.

Plan for the known, but also plan for the fact that there will be unknowns that could take more time than the known’s when launching on any new platform. It’s also important to de-risk your project ASAP for any showstopper technical issues. It’s easy to think you’ve done that with your local development setup but if your production environment looks pretty different then you really haven’t, so test your prod environment sooner rather than later. I learned this lesson on Macrocosm with challenges in the prod build on the app store, but it was a mistake to think it wouldn’t be as big of an issue this time.

And of course, if you’ve been falling out of love with your search results I’d love it if you’d give Cat Search a whirl and let me know what you think! It’s definitely in beta state and there’s a long list of stuff I want to improve or add already of course but hearing real feedback is more important than jumping right into that. The feedback button is in the top right when using it.

Want the inside scoop?

Sign up and be the first to see new posts

No spam, just the inside scoop and $10 off any photo print!