<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Martin Danielson</title>
    <description>The latest articles on Forem by Martin Danielson (@martindanielson).</description>
    <link>https://forem.com/martindanielson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2760250%2F83625d6e-97a3-4e65-9a14-96c55d5688a6.jpeg</url>
      <title>Forem: Martin Danielson</title>
      <link>https://forem.com/martindanielson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/martindanielson"/>
    <language>en</language>
    <item>
      <title>Micro-frontends with AI - it solves everything</title>
      <dc:creator>Martin Danielson</dc:creator>
      <pubDate>Sun, 26 Apr 2026 12:10:53 +0000</pubDate>
      <link>https://forem.com/martindanielson/micro-frontends-with-ai-it-solves-everything-fo8</link>
      <guid>https://forem.com/martindanielson/micro-frontends-with-ai-it-solves-everything-fo8</guid>
      <description>&lt;p&gt;&lt;em&gt;This article covers a few important tips to consider before choosing micro-frontends for your tech stack. I also cover what other tools fit well with it (AI) but most importantly what you need to always practice no matter if you choose micro-frontends or another tech to create modularization.&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;"Understanding the problem and finding a solution for that specific problem is vital"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;First of all, let me explain &lt;strong&gt;the problem I was trying to solve&lt;/strong&gt;. Understanding the problem and finding a solution for that specific problem is vital in order to not end up with a platform with bloated features and unnecessary complexity. &lt;br&gt;
Reading up on &lt;em&gt;micro-frontends&lt;/em&gt; can also be a little confusing since it can solve a number of problems and there are a lot of different - both in flavors and in numbers - actors in this space. &lt;br&gt;
You have to be careful - as always, when adopting new technologies - trusting the blatant claim that “this tech is the best”; especially when picking a &lt;em&gt;micro-frontend&lt;/em&gt; framework.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We actually spent most of the time fixing bugs in our testing framework"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What we initially set out to do was to do &lt;strong&gt;legacy displacement of tech&lt;/strong&gt;, meaning we had an existing platform that over the years had become a “house of cards” and accumulated so much technical debt that it created more problems than we could handle. &lt;br&gt;
We actually spent most of the time fixing bugs in our testing framework for the application - yes, read that sentence again; it was that bad - than actually delivering new features or value to the customers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"avoid a “big bang” and slowly transition to the new technical platform"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, we wanted to replace and re-write parts of the application to avoid a “big bang” and slowly transition to the new technical platform. This is almost a must when trying to convince stakeholders of going down this path.&lt;/p&gt;

&lt;p&gt;This did not necessarily require &lt;em&gt;micro-frontends&lt;/em&gt;, it could be done way easier using normal routing and sane code structure. Just some good old hard work, simply fixing the existing code. However; since there were so many issues with the code-base and after talking with some colleagues who had tried out &lt;em&gt;micro-frontends&lt;/em&gt; recently, we decided to give it a try.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"what micro-frontend tech to use"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we needed to decide &lt;em&gt;what&lt;/em&gt; &lt;em&gt;micro-frontend&lt;/em&gt; technology to use. And here is where it gets a bit confusing - or even messy at times - and you really need to &lt;strong&gt;pay attention how the framework is implemented and possible caveats with the solutions&lt;/strong&gt;. &lt;br&gt;
I will not attempt to summarize the whole landscape in this article - that is too huge of a topic to cover - I am trying to get another point out there. But I will still name a few things to consider for those of you who are currently considering implementing &lt;em&gt;micro-frontends&lt;/em&gt; for your organization.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Micro-frontends&lt;/em&gt; is all about running smaller components inside a single container. Separating these components and making them runnable on their own is a lot what this technology is trying to provide. But trust me it is very complicated to solve this in the &lt;em&gt;JavaScript&lt;/em&gt; eco system with how bundles are handles, dependencies are loaded and how code is shared and not isolated. &lt;br&gt;
&lt;strong&gt;I rarely express that something in software engineering is complicated&lt;/strong&gt; - but the orchestration of multiple (at least in our case) independent frontend frameworks, all co-existing in the same single page application, has so many areas where it can go wrong I would &lt;strong&gt;carefully consider the need&lt;/strong&gt; before recommending this to anyone. As I said earlier, make sure you truly understand what problem you are trying to solve.&lt;br&gt;
Comparing this to &lt;em&gt;micro-services&lt;/em&gt; i.e. tt is way more complicated since everything needs to run in the same browser on a client - where-as micro-services all run independently and then just deliver the result to a client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client vs. server&lt;/strong&gt; side capabilities. In recent times the popularity of meta frameworks that both render on client &amp;amp; server has grown a lot. &lt;em&gt;Micro-frontends&lt;/em&gt; is somewhat of an old technology actually and many of the frameworks was built with client side only at mind. So, you need to consider this because orchestrating this alone is hard - adding hydration between client &amp;amp; server adds exponential complexity. Not to mention having to deal with all the &lt;em&gt;CORS&lt;/em&gt; and security rules in modern browsers. If you can, opt for a framework that has server side rendering.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Isolation&lt;/strong&gt;. It is pretty funny to me that still to this day true isolation can only be achieved with the (in)famous &lt;code&gt;IFRAME&lt;/code&gt;. We would probably have considered using it to some extent if it was not for the issues related to privacy. As with cookies and shared domains many browsers have increased the security by disabling a lot of what can be done using these technologies; so unfortunately it was not an option for us. &lt;br&gt;
So, micro-frontends does not come with true isolation. Since it is living in the same document there is noting that prevents one component from affecting another through either &lt;strong&gt;&lt;em&gt;CSS&lt;/em&gt; "bleed-out" or the use of &lt;em&gt;JavaScript&lt;/em&gt;&lt;/strong&gt; to reach different parts of the document. &lt;br&gt;
This can be controlled with careful implementation though, i.e. using prefixed &lt;em&gt;CSS&lt;/em&gt; rules or even more modern things such as layers - but there is no guarantee and since we planned to gradually transition - and by this co-existing with an extremely badly written legacy application - it took a lot of efforts to just get basic boundaries set up. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid mixing multiple &lt;em&gt;JavaScript&lt;/em&gt; frameworks. We wanted to change from &lt;em&gt;Angular&lt;/em&gt; to &lt;em&gt;React&lt;/em&gt; but you can save a lot of head-ace if you do not need to support multiple (any) frameworks. There are simpler implementations that provides most of the micro-frontend capabilities if you can share frontend technology. &lt;br&gt;
This is another thing I would &lt;strong&gt;advice against; if you are choosing &lt;em&gt;micro-frontends&lt;/em&gt; as a means to be able to run any framework&lt;/strong&gt;. This might sound like a good idea in order to give autonomy to teams and allow them to chose technologies freely - but this will create a lot of extra complexity since the frontend frameworks needs to be supported by the &lt;em&gt;micro-frontend&lt;/em&gt; framework and there are no guarantees that a framework will be maintained and able to support any of the new features a frontend library decides to implement. &lt;strong&gt;We are actually planning to use our &lt;em&gt;micro-frontend&lt;/em&gt; setup to move over to &lt;em&gt;React&lt;/em&gt; only, as a transition, and then stick with &lt;em&gt;React&lt;/em&gt; once all legacy has been replaced&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration heavy&lt;/strong&gt; and lack of maintenance. To keep it short; there is a lot of configuration involved solving all things related to &lt;em&gt;micro-frontends&lt;/em&gt;, &lt;em&gt;CORS&lt;/em&gt;, &lt;em&gt;CSP&lt;/em&gt;, deployment, building, runtime etc. Be prepared to really dig deep in most likely multiple build tools in order to set everything up correctly.&lt;br&gt;
Also, remember that the micro-frontend technology you choose will become a sort of single point of failure. And there is nothing to guarantee that it will be maintained. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Wow, this did become a lot more information on the implementation than I intended but these are all important learnings so I think it is good to include nonetheless.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Part 2 - super-charge your AI capabilities
&lt;/h2&gt;

&lt;p&gt;But, &lt;strong&gt;back to my original trail of thoughts&lt;/strong&gt;. So, now we had decided on &lt;em&gt;micro-frontends&lt;/em&gt; and we also had our pick of the framework (&lt;strong&gt;&lt;a href="https://single-spa.js.org/" rel="noopener noreferrer"&gt;single-spa&lt;/a&gt;&lt;/strong&gt;). This is the point where we started to see an emerging problem replacing only parts of the old application with new micro-frontends. Let me try to visualize this more clearly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3tkn0mlsbd2nlz86mh3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3tkn0mlsbd2nlz86mh3.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine a mail client, with a top menu, side bar and then a reading pane. In the micro-frontend container (the “host” or “root” if you will) I can say that the top menu should be served from micro-frontend A, the side bar from micro-frontend B and the reading pane from micro-frontend C. Easy. But, what if we wanted to replace only the attach file component inside the reading pane, while keeping the rest from micro-frontend C?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do you see the problem?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is exactly what we wanted, &lt;strong&gt;we wanted - to replace the smallest parts of our legacy application but still use everything else from it&lt;/strong&gt;; and we needed a way to do that. Luckily &lt;em&gt;single-spa&lt;/em&gt; has something for just this case. It is called &lt;em&gt;Parcels&lt;/em&gt; and basically what it does is it creates a placeholder in the legacy application that it will then inject the micro-frontend in. Looking at the solution it was not really rocket science or anything but it was something we did not really think of initially and we were happy (lucky) to see it was part of the technology we had chosen.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Now we had a framework where we could surgically replace any parts of an old application"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So now we got to work. And with the aid of AI we started to really quickly replace parts of the legacy platform. &lt;strong&gt;With the power of AI we dared more and was able to be extremely productive and fast transitioning to the new platform&lt;/strong&gt;. &lt;br&gt;
Not only did we replace components we also gave them a touch up in both functionality and visuals. And this is what I wanted to emphasize on in this article - finally - that actually what made this endeavors so successful and powerful is actually three (3) things - that all are equally important. We have &lt;em&gt;&lt;strong&gt;micro-frontends&lt;/strong&gt;&lt;/em&gt; on one end, then &lt;strong&gt;AI&lt;/strong&gt; on the other - can you guess what is the third?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The problem with AI is that doing large scale re-factoring very error-prone and takes &lt;em&gt;A LOT&lt;/em&gt; of time"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;It is the size of the changes. Being able to replace smaller parts of the legacy application made AI actually usable.&lt;/strong&gt; One could have argued earlier that one approach to solve the problem we had was to tell AI (without micro-frontends) to re-write the entire application in a new technology. &lt;br&gt;
The problem with AI is that doing large scale re-factoring very error-prone and takes &lt;em&gt;A LOT&lt;/em&gt; of time. &lt;br&gt;
We have had experiences where we have tried just to update certain parts to &lt;em&gt;TypeScript&lt;/em&gt; &lt;em&gt;strict&lt;/em&gt; or just upgrade from one version of &lt;em&gt;.NET&lt;/em&gt; to another and it took us weeks to complete (to be honest some parts is still on-going).&lt;/p&gt;

&lt;p&gt;But, when the things we are replacing are small, then AI can focus a lot more and we can make sure we have feature parity with less effort. And since we are free to pick and choose any framework for each micro-frontend we can guide AI to give us a solution that is well written and a lot easier to maintain and change in the future.&lt;/p&gt;

&lt;p&gt;I did not actually know how important the word "micro" was in micro-frontends but now I see the whole picture and I hope I could inspire others to take the leap, go through the initial hassle and time to set this kind of architecture up. Because it did take some time to get everything working just right - but now I have no regrets at all going down this line.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you have any question regarding the actual implementation or want me to dive deeper into a topic - please let me know. I would gladly like to share.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>frontend</category>
      <category>software</category>
      <category>microfrontends</category>
    </item>
    <item>
      <title>Will AI take over my job as a software engineer</title>
      <dc:creator>Martin Danielson</dc:creator>
      <pubDate>Sun, 26 Apr 2026 07:10:29 +0000</pubDate>
      <link>https://forem.com/martindanielson/will-ai-take-over-my-job-as-a-software-engineer-1nco</link>
      <guid>https://forem.com/martindanielson/will-ai-take-over-my-job-as-a-software-engineer-1nco</guid>
      <description>&lt;p&gt;This article expresses some of my thoughts as a senior software engineer working as a consultant in an era of AI where agents is doing “all the work” and the job market is fluctuating, the world is in crisis and there is a lot of uncertainty and fear about what the future holds. &lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;"I cannot not judge anything unless I know what it actually is"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;I am a curious person&lt;/strong&gt;. I have most my life spent the better part of it exploring new things - both things that intrigue me but also things I fear. My reasoning is that I cannot not judge anything unless I know what it actually is. This kind of curiosity can be a little dangerous too - but thankfully I am also pretty strong-willed so I have managed to get out from exploring things that might not be so good for me (addiction i.e.).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"there is a lot of fear going around now that AI will replace many tasks that currently humans do"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not one of those things though - but there are many fears involved with AI. For one, I grew up watching all the &lt;em&gt;Terminator&lt;/em&gt; movies so I know that eventually they will come to life and kill me. Maybe not, but there is a lot of fear going around now that AI will replace many tasks that currently humans do. Another fear tied to AI, from one practicing it daily in my work, is that I will &lt;strong&gt;stop learning&lt;/strong&gt; and become even less attractive to the job market since I am no longer acquiring knowledge - in a way they are both grounded in the same fear I guess.&lt;/p&gt;

&lt;p&gt;This is my take on it, and please understand I am just starting out in my venture with AI and this is just me thinking out loud. &lt;strong&gt;I am actually not using AI to write this&lt;/strong&gt;. Nor am I trying to earn clicks or subscriptions to any channel. I am however interested in hearing what other people in a similar situation is thinking about this.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am now not writing any code at all , I have become what the media calls a “prompt engineer”"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As I started saying I have a need to understand everything, especially if I am to express negative views on a subject. So, I have for the past 4 weeks dove deep into using AI in my professional life. I have been dabbling on and off with it over the past year and a half though - but not like this. I am now not writing any code at all , I have become what the media calls a “prompt engineer” - or if you want to put a negative spin on it a “&lt;strong&gt;vibe coder&lt;/strong&gt;”.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"what is my worth now when I am using a tool to cover all the skills I have so proudly been bosting in my CV for the past 30 years"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With this I keep thinking along the lines of “what is my worth now when I am using a tool to cover all the skills I have so proudly been bosting in my CV for the past 30 years”. If I am letting a tool do all the work I used to do - writing good code, organizing it neatly, reading up on new tech, trying to learn how things work in order to teach others, planning infrastructure and thinking about performance and scalability - then &lt;strong&gt;why would someone pick me over someone else with the same tool&lt;/strong&gt;? I mean, I feel that all my time I have spent studying and learning to become a great software engineer is sort of thrown out the window. Anyone can do it - no seriously, I truly believe anyone could literally do it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am sort of an architect"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To this I don’t really have any comforting answer. AI today is not all there, it does mistakes, it lacks context and sometimes critical thinking and it needs a lot of guidance. This somewhat puts me at ease for the short term since this is where all my knowledge still come into play. &lt;strong&gt;I can steer the AI to make better choices where it is lacking&lt;/strong&gt;. I can immediately spot overcomplicated code, I can imagine how this would be maintained and make sure that AI produces something that is easy to evolve and take over. I could tell it to write tests and not forget to lint the code. I am sort of an architect in a sense - telling it what I want out of it from a technical and maintainability point of view.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am allowing AI to take over, write all the code"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, I still get to use some of the knowledge I have. But I think there is only a matter of time before AI understands these aspects too and truly will be able to produce code equally - if not better - than someone with 30 years of professional experience within software engineering. As a result of this, I am trying to evolve my thinking on this subject. I am letting go of what I have held so dear - my knowledge and experience. I am allowing AI to take over, write all the code and trying to figure out something that it could not replace. &lt;strong&gt;This is a bit scary indeed, and there is a lot of imposter syndrome floating around&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am actually enjoying this very much"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But I want to highlight something here. &lt;strong&gt;This is fun also&lt;/strong&gt;. First of all I am still learning new things. &lt;strong&gt;And I get to be super creative to a level I have not been able to before&lt;/strong&gt;. I mean, I have been able to write new features, both requested but also things that I just come up with in the moment, super fast and with a lot of freedom to explore. I can think of an idea, make AI implement it and then throw it away and do something else without feeling I am throwing away 2 days of CSS tinkering and coding. I can try out a new framework on a whim and see it in action instead of spending hours on researching it and sifting through flame war articles about which tech is better than the other’s. I am actually enjoying this very much and it makes it a lot easier to handle the imposter syndrome and still find value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actually, this is something I have long lacked in my professional career. To feel truly productive, daring and exploring possibilities. Not just being told what to do, maybe AI is enabling this for me, maybe this is a really a blessing and not a curse.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What do you think? Is AI just a passing thing, is our fears unfounded? Do you have any other interesting thoughts on the subject? Let me know please and have a wonderful day.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>developers</category>
      <category>psychology</category>
      <category>software</category>
    </item>
    <item>
      <title>Generate HTML as PDF using Next.js &amp; Puppeteer running on Serverless (Vercel/AWS Lambda) Martin Danielson</title>
      <dc:creator>Martin Danielson</dc:creator>
      <pubDate>Fri, 24 Jan 2025 22:10:35 +0000</pubDate>
      <link>https://forem.com/martindanielson/generate-html-as-pdf-using-nextjs-puppeteer-running-on-serverless-vercelaws-lambda-martin-4jkp</link>
      <guid>https://forem.com/martindanielson/generate-html-as-pdf-using-nextjs-puppeteer-running-on-serverless-vercelaws-lambda-martin-4jkp</guid>
      <description>&lt;p&gt;I wanted to share my experience of generating PDF’s from web pages using &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt;, deployed on &lt;a href="https://vercel.com/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; (or &lt;a href="https://aws.amazon.com/lambda" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt;). While there are many resources available on similar topics, I found that none of them provided a complete solution. Since this is a common use case — whether for invoicing, scraping, or testing — I hope my insights can help others facing similar challenges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Background&lt;/strong&gt;: One of my businesses rents out a space in a venue, and while we only generate about 30 invoices per year, it can become tedious work and prone to errors. So, I decided to build an app to automate invoice generation based on HTML (which is easier for me to work with since I am familiar with front-end development). I also needed the system to send invoices by email, manage different billing periods, and be easy to extend with new customers.&lt;/p&gt;

&lt;p&gt;Rather than focusing on all the logic behind calculating when and how to send invoices, I will focus here on how I set up the PDF generation and hosted the entire solution on Vercel. Mind you that all of this is from memory, so I could not provide exact error codes and might have missed some details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up PDF Generation with Puppeteer
&lt;/h3&gt;

&lt;p&gt;I created a simple Next.js page to render my invoices. The next step was converting the HTML of a page into a PDF. I have used Puppeteer before, and after some research, I found it was still one of the best options for this use case.&lt;/p&gt;

&lt;p&gt;Here’s the basic code to generate the PDF from a URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000/invoice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a.pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue with Puppeteer and Serverless limits
&lt;/h3&gt;

&lt;p&gt;The code above worked perfectly locally, but when I deployed it, the endpoint returned a &lt;strong&gt;500&lt;/strong&gt; error.&lt;/p&gt;

&lt;p&gt;Puppeteer relies on Chrome to render pages, and the Chrome binary included in the Puppeteer package is too large for serverless functions due to size constraints.&lt;/p&gt;

&lt;p&gt;After some digging, I opted to use &lt;strong&gt;puppeteer-core&lt;/strong&gt; — which lets you specify your own browser — together with a version of Chromium called &lt;a href="https://github.com/Sparticuz/chromium#-min-package" rel="noopener noreferrer"&gt;@sparticuz/chromium-min&lt;/a&gt;, which is tailored for serverless environments. However, even this was too large to upload directly to my project (over 50MB).&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution: Using external storage for Chromium
&lt;/h2&gt;

&lt;p&gt;The solution was to host the smaller Chromium binary in an external Blob storage (same I used to save the PDF’s in) and reference it during runtime. Here’s how I configured Puppeteer to use this custom Chromium version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;URL_to_chromium-min_tar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, to ensure that Puppeteer runs in development with the standard Chromium version (useful for debugging), I used this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;executablePath&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;puppeteer-core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;URL_to_chromium-min_tar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling timeouts and optimizing the process
&lt;/h3&gt;

&lt;p&gt;When running in a serverless environment, Puppeteer can be slow due to the overhead of downloading, unpacking and loading Chromium.&lt;br&gt;
Just loading the browser took about 15 seconds, which led to timeouts, not counting to load a page and generate the PDF’s.&lt;/p&gt;

&lt;p&gt;Vercel’s default serverless timeout is 10 seconds. This can easilly be extended to 60 seconds on their free tier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However; this was not enough to generate a number of PDF’s so I also made two optimizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. &lt;strong&gt;Singleton pattern&lt;/strong&gt;: Reused the browser instance across multiple PDF generations to reduce overhead.&lt;/li&gt;
&lt;li&gt;2. &lt;strong&gt;Optimized &lt;code&gt;waitUntil&lt;/code&gt; parameter&lt;/strong&gt;: I switched from &lt;code&gt;networkidle2&lt;/code&gt; (which waits for all network activity to finish) to the default &lt;code&gt;load&lt;/code&gt; event, which is more efficient for my use case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, instead of writing the PDF to disk and reading it back, you can use the buffer that is returned and pass it to Vercel’s Blob storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Set Extra HTTP Headers for Vercel&lt;/strong&gt;: When running on Vercel, your resources are protected on some domains (i.e. your priview environments). This led to me generating a lot of PDF’s of the Vercel login page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setExtraHTTPHeaders&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-vercel-protection-bypass&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;VERCEL_AUTOMATION_BYPASS_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;CSS for PDF Layout&lt;/strong&gt;: To ensure the PDF renders in a print-friendly format and without margins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;preferCSSPageSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;@page&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;A4&lt;/span&gt; &lt;span class="nb"&gt;portrait&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Securing Your Endpoint&lt;/strong&gt;: I tried securing the API endpoint using the Authorization header, but Vercel has an issue with forwarding headers when using rules. The authorization header is passed under x-vercel-sc-headers, but it’s not guaranteed to work reliably. I decided to leave it for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;If you’ve made it this far, thank you! I hope this guide helps anyone trying to generate PDF’s from HTML using Puppeteer in serverless environments like Vercel.&lt;/p&gt;

&lt;p&gt;I welcome suggestions and tips. Also if you feel that I did not credit some resource or author please contact me and I will accredit accordingly.&lt;/p&gt;

&lt;p&gt;Credits to resources I used to figure all this out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/hajek-raven/d05204c948f773bbeff311b681a79df8" rel="noopener noreferrer"&gt;https://gist.github.com/hajek-raven/d05204c948f773bbeff311b681a79df8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thebiltheory.com/blog/use-puppeteer-on-aws-lambdas" rel="noopener noreferrer"&gt;https://www.thebiltheory.com/blog/use-puppeteer-on-aws-lambdas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>puppeteer</category>
      <category>pdf</category>
      <category>vercel</category>
    </item>
  </channel>
</rss>
