<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Alexander Lichter | Blog</title>
        <link>https://www.lichter.io</link>
        <description>undefined</description>
        <lastBuildDate>Wed, 22 Apr 2026 18:59:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>;)</generator>
        <image>
            <title>Alexander Lichter | Blog</title>
            <url>https://www.lichter.io/__og-image__/image/articles/og.png</url>
            <link>https://www.lichter.io</link>
        </image>
        <copyright>Code licensed under MIT, written content licensed under CC-BY-NC-SA 4.0 - Alexander Lichter</copyright>
        <atom:link href="https://www.lichter.io/feed.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2023 - My Recap]]></title>
            <link>https://www.lichter.io/articles/2023-my-recap/</link>
            <guid>https://www.lichter.io/articles/2023-my-recap/</guid>
            <pubDate>Sun, 31 Dec 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[It is time to recap the year 2023! I want to share my personal and professional journey through the last 365 days, as well as my goals for 2024. From moving to another country over jumping back into content creation. Hard facts, numbers and insights included - I promise!]]></description>
            <content:encoded><![CDATA[<p><!--[-->Hey hey! It's the end of 2023 and I want to recap the year. Since doing this kind of post the first time in 2018, I wanted to continue this tradition and do it again each year. Somehow, it didn't happen until now - five years after. But better late than never, right?
Below you can find a recap of my year 2023 in different categories. I hope you enjoy it!<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Personal Life<!--]--></a><ul><!--[--><li><a><!--[-->Moving to a new Country 🇳🇱<!--]--></a><!----></li><li><a><!--[-->Traveling to unknown Places ✈️<!--]--></a><!----></li><li><a><!--[-->Social Connections<!--]--></a><!----></li><li><a><!--[-->Studying<!--]--></a><!----></li><li><a><!--[-->Health<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Open Source<!--]--></a><!----></li><li><a><!--[-->Speaking<!--]--></a><!----></li><li><a><!--[-->Content Creation<!--]--></a><ul><!--[--><li><a><!--[-->Personal Branding and Website Rewrite<!--]--></a><!----></li><li><a><!--[-->YouTube + Twitch<!--]--></a><!----></li><li><a><!--[-->Newsletter<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Business<!--]--></a><!----></li><li><a><!--[-->Goals and plans for 2024<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Personal Life<!--]--></a></h2><h3><a><!--[-->Moving to a new Country 🇳🇱<!--]--></a></h3><p><!--[-->My personal life changed quite at bit! After committing to a long-distance relationship (Leipzig - Amsterdam) last year, it was time for the next step. Eight hours of train ride every two weeks could be worse, but it was still a lot. And the time in between when not seeing each other was also not ideal. In addition, having no "main home" was not for me, especially because I had quite some plans that need a steady home base.<!--]--></p><p><!--[-->So, time for a change! And because I can work from anywhere, I decided to move to Amsterdam in April. While it wasn't easy to "leave my friends and family behind" - just physically of course, I'm really happy with the decision. Especially because it isn't the other side of the world and I can still visit them regularly in not even a day of traveling.<!--]--></p><p><!--[-->Finding a flat wasn't that easy. But thanks to being a web developer, I could <em><!--[-->optimize<!--]--></em> a lot of the flat hunting. I even <a><!--[--><!--[-->gave a talk<!--]--><!--]--></a> on how I did it!<!--]--></p><p><!--[-->Amsterdam is a beautiful city, and I'm really happy to live here. Not too big (hey Berlin!), but also not too small - beautiful architecture, vegetation and full of bike roads. Not that we don't have them in Germany, but it's a different level here. And I love it!<!--]--></p><p><!--[-->The cultural differences compared to Germany are not that big and you can get around with English quite well. Learning Dutch is on the list, but priority-wise it wasn't that high for 2023. For 2024, it is part of my goals though!<!--]--></p><p><!--[-->One thing that was a bit unusual for me though - flats in the Netherlands often come without a floor! So, buying and installing the laminate was one of the first things on the list.<!--]--></p><figure><picture><!--[--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><img><!--]--><!--]--></picture><figcaption>Niki and me standing in our apartment without flooring, but with the keys!</figcaption></figure><h3><a><!--[-->Traveling to unknown Places ✈️<!--]--></a></h3><p><!--[-->Besides getting to know Amsterdam better and better, I also had the opportunity to travel to new places and even new countries! From going to Brussels for a long weekend <a><!--[--><!--[-->meeting with a good friend<!--]--><!--]--></a> and doing some sightseeing, to traveling to Cambridge and Oxford after a conference, I've seen a lot. One of the most amazing trips this year was to Toronto, Canada. Before <a><!--[--><!--[-->speaking at VueConf Toronto<!--]--><!--]--></a>, I had the chance to explore the city and meet some amazing people. In addition, I also had the chance to visit the Niagara Falls, which was a great experience. Maybe a bit cold (4°C), but still a breathtaking view.<!--]--></p><figure><picture><!--[--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><img><!--]--><!--]--></picture><figcaption>Drinking a cold beverage with the Niagara Falls in the background</figcaption></figure><p><!--[-->A travel of another kind was to Tomorrowland - my only music festival this year. I've been to a few in the past, but this one was the first time at Tomorrowland. And it was spectacular! The atmosphere, the people, the music - everything was great. I'm really looking forward to going there again next year - if my luck is good enough to get tickets again.<!--]--></p><h3><a><!--[-->Social Connections<!--]--></a></h3><p><!--[-->Even though I moved to a new country, I still had the chance to meet lots of amazing people. From like-minded new faces at conferences and meetups, to old friends and family. Of course, I visited my hometown every now and then to ensure I don't lose contact with my friends and family there.<!--]--></p><p><!--[-->And it is always fascinating to meet the people you know from the internet in person. Be it with parts of the Nuxt team in Amsterdam, or finally connecting in real life at events - it's always a great experience.<!--]--></p><figure><picture><!--[--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><img><!--]--><!--]--></picture><figcaption>Meeting up with (from left to right) Anthony Fu, Pooya Parsa and Daniel Roe in Amsterdam</figcaption></figure><h3><a><!--[-->Studying<!--]--></a></h3><p><!--[-->Ehh, yes. That might be a weird fact (sometimes even fun fact), but I am <em><!--[-->still<!--]--></em> studying at the university in Dresden. Nevertheless, I'm finally at the end of my studies and started writing my thesis this fall. I hope to finish it in the first quarter of 2024 and finally get my degree - but a few more pages (read: at least sixty) are between me and that goal. So, wish me luck!<!--]--></p><h3><a><!--[-->Health<!--]--></a></h3><p><!--[-->My routines changed to the worse in the first part of the year. Due to the commuting and then moving, my sport routines really suffered. Same for my eating habits. But towards the end of the year, I slowly found my way back on track to establishing a healthy lifestyle. I started going to the gym again, and the nutrition improved a bit as well. But there is still way to go, making this one of my main goals for 2024.<!--]--></p><p><!--[-->My work-life balance, on the other hand, is still good. The only thing that could go better there (and in general)is my time management and planning skills.<!--]--></p><h2><a><!--[-->Open Source<!--]--></a></h2><p><!--[-->2023 was a good year for my open source contributions. While the move impacted my time for open source, I still contributed a lot. As usual, most of it in the Nuxt.js and UnJS ecosystem, but also to other projects if I saw some low-hanging fruit.<!--]--></p><figure><picture><!--[--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><img><!--]--><!--]--></picture><figcaption>My GitHub contribution graph for 2023 showing a total of 1880 contributions</figcaption></figure><p><!--[-->It is important to mention that most of my contributions are not code-related and therefore <strong><!--[-->not fully covered by the GitHub graph<!--]--></strong>. Most of my tasks I'd described as "community engineering and management" tasks. They range from triaging issues and PRs, answering questions on Discord, Twitter and other channels, to improving documentation and more. Non-code contributions are an <em><!--[-->essential<!--]--></em> and important part of open source, and I'm happy to help with it.<!--]--></p><p><!--[-->Of course, I also shared my knowledge at conferences, rewrote my website and even started creating more content again. I'll cover that in the next two sections - <a><!--[--><!--[-->Speaking<!--]--><!--]--></a> and <a><!--[--><!--[-->Content Creation<!--]--><!--]--></a>.<!--]--></p><h2><a><!--[-->Speaking<!--]--></a></h2><p><!--[-->Speaking is one of the things I really enjoy. I love to share my knowledge and experience with the community and help others to learn something new. I had the chance to speak at a number of conferences and meetups, as well as partially MC'ing two larger ones.<!--]--></p><p><!--[--><strong><!--[-->In numbers<!--]--></strong>, I gave <strong><!--[-->13 talks<!--]--></strong> and was part of <strong><!--[-->2 panels<!--]--></strong> at <strong><!--[-->4 meetups<!--]--></strong> and <strong><!--[-->9 conferences<!--]--></strong>.
In addition, I was an MC at <strong><!--[-->WeAreDevelopers<!--]--></strong> and <strong><!--[-->Vue.js Amsterdam<!--]--></strong>. You can find the full list on the <a><!--[--><!--[-->speaking page<!--]--><!--]--></a> here!<!--]--></p><p><!--[-->Fun fact: The busiest conference was WeAreDevelopers, where I was MC'ing, part of a panel and gave <em><!--[-->two<!--]--></em> talks. That was a lot of joy, but also a lot of work.<!--]--></p><p><!--[-->I definitely want to continue speaking at conferences and meetups in 2024. I had a blast and it is a great way to meet new people and share knowledge. I don't have a definitive number of talks I want to give in 2024, but I think 8 is a good rough estimate.<!--]--></p><h2><a><!--[-->Content Creation<!--]--></a></h2><h3><a><!--[-->Personal Branding and Website Rewrite<!--]--></a></h3><p><!--[-->Content creation was always something I enjoyed and wanted to do more. In 2018, I started writing blog posts about Nuxt and Vue regularly but eventually stopped due to the lack of time. One of the issues that kept me from releasing a blog post was my self-built blog which simply became legacy. Eventually, my backend server expired rendering me unable to update a single line of content. And yes, this all screamed one thing: <strong><!--[-->Rewrite<!--]--></strong>!<!--]--></p><p><!--[-->The goal was to merge my website and blog into one (before the blog was a separate Nuxt app on a subdomain), give everything a modern design and switch to Nuxt content. So, with a plan in mind I started to create the website you are reading this post on now. As before, I wanted to open-source it. But there was more behind it.<!--]--></p><p><!--[-->Another goal I wanted to achieve was to improve my personal branding. The process started with a modern logo incorporating the lightbulb - which was always part of my online identity. I also wanted a consistent design across all my channels, so I created a new banner for Twitter, YouTube, Twitch and more.<!--]--></p><p><!--[-->This all launched in the last week of September, where I created a "launch week" event. From Monday to Friday, I deployed my new website, released two new blog posts, did a live stream guiding through the website's code, eventually open-sourced it, and then revealed another project I was working on: My <strong><!--[-->YouTube channel<!--]--></strong>.<!--]--></p><h3><a><!--[-->YouTube + Twitch<!--]--></a></h3><p><!--[-->Yes, the <a><!--[--><!--[-->YouTube channel<!--]--><!--]--></a> was one of the things that I wanted to do for a long time. I always enjoyed creating videos and sharing my knowledge. But first, I lacked the time and consistent branding. After that I lacked a "home" to record my videos in. I decided if I want to tackle it, I need to do it right and consistent. After the move to Amsterdam, the upcoming months were occupied with stuff revolving around the flat, furnishing it and making it a home. And then, I finally had a place to record videos.<!--]--></p><p><!--[-->And, oh boy, did I look forward to it. Even though the launch week event described in the previous subsection was a lot of work, it sparked so much joy. Creating videos became a passion, and thanks to <a><!--[--><!--[-->Niki<!--]--><!--]--></a> (my girlfriend), I also had someone to help me with the editing, not only making it look more professional and saving some time, but also letting each of us shine on what we can do best.<!--]--></p><p><!--[-->If you <a><!--[--><!--[-->haven't checked the channel yet<!--]--><!--]--></a> - I am focusing on intermediate and advanced content around Vue, Nuxt, UnJS, and the JS and TS ecosystem in general. I noticed a lot of beginner content is out there, but as soon as things become more complex or advanced, there is a high demand, but not enough content to match it. So, I want to fill that gap and create content matching the niche. Because of my expertise, I thought this is even more suitable, and I can help the community that way.<!--]--></p><p><!--[-->Besides the YouTube channel, <a><!--[--><!--[-->Twitch and livestreaming<!--]--><!--]--></a> was another big topic for me. But after three consecutive streams I realized it was tricky to stick to a regular schedule for these. While you can flexibly produce YouTube clips, streaming means (ideally) having a fixed timeslot every week, and due to conferences, work and other commitments, it wasn't feasible for me. Nevertheless, I want to stream a few more times next year but don't want to commit to a regular schedule.<!--]--></p><p><!--[--><strong><!--[-->In numbers<!--]--></strong>, I published <strong><!--[-->16 videos<!--]--></strong> - 14 original videos (once a week) and two uploaded livestream VODs from Twitch. The videos now have <strong><!--[-->over 27.000 accumulated views<!--]--></strong>, and the channel reached <strong><!--[-->1.500 subscribers<!--]--></strong> before the end of 2023. These are staggering numbers - I didn't expect such a great start.In addition, I did not set any goal for the channel except being consistent with releases, so I'm even more delighted about the outcome and all the positive feedback and constructive criticism I got.<!--]--></p><p><!--[-->Thanks to everyone for the support and feedback, as well as for the ideas for new videos. PS: If you have some, please let me know!<!--]--></p><figure><picture><!--[--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><source><!--]--><!--[--><img><!--]--><!--]--></picture><figcaption>YouTube stats for 2023 showing 16 videos, 27.000+ views and 1.500+ subscribers</figcaption></figure><h3><a><!--[--><del>Newsletter</del><!--]--></a></h3><p><!--[--><del>And yes, I also started a newsletter with the website launch, which I want to continue and actually highlight in 2024. I'm not sure yet how often I want to send it out - so far, I didn't due to the lack of time. But I hope to find a good schedule for it in 2024.</del><!--]--></p><p><!--[--><strong><!--[-->Update:<!--]--></strong> The newsletter didn't work out — I didn't have the time to keep it going, so I've shut it down.<!--]--></p><h2><a><!--[-->Business<!--]--></a></h2><p><!--[-->Open source is lots of fun, but it doesn't fully pay the bills. At this point, I want to thank <a><!--[--><!--[-->all my sponsors<!--]--><!--]--></a> and also past sponsors for their support. It means a lot to me, and I'm really grateful for it.<!--]--></p><p><!--[-->The main chunk of my income comes from my work at my own company through <a><!--[--><!--[-->consulting<!--]--><!--]--></a> and <a><!--[--><!--[-->workshops<!--]--><!--]--></a> - and I think it is the perfect counterpart to open source. I can work with companies and help them with their projects while also learning a lot from them and their use cases. That allows me to understand how they use Nuxt.js, which pain points they experience and how I can help them. On the other hand, they also expect me to know the guts of Nuxt.js and how it works internally - a perfect symbiosis.<!--]--></p><p><!--[-->In 2023, I worked together with incredible people and companies. Business-wise, 2023 was a great year, even with quite some time spent on the move and the economic crisis. With a good number of clients as wells as more than 12 workshops (at conferences and for businesses), I'm really pleased with the outcome of 2023.<!--]--></p><h2><a><!--[-->Goals and plans for 2024<!--]--></a></h2><p><!--[-->To finish this years recap, I want to set some goals for 2024 that I want to achieve. Some are "SMART" but some goals might not have a fixed number attached to them. While this might not be the best way to set goals, I think there are some goals that are not measurable but still achievable.<!--]--></p><ul><!--[--><li><!--[-->Personal
<ul><!--[--><li><!--[-->Get a good gym routine, going at least once a week without more than two weeks of break (due to conferences, holidays, etc.)<!--]--></li><li><!--[-->Lose weight and get in shape - at least 10kg - and keep it below that<!--]--></li><li><!--[-->Travel to two or more new places I haven't been to yet<!--]--></li><li><!--[-->Continue to learn Hungarian<!--]--></li><li><!--[-->Start learning Dutch<!--]--></li><li><!--[-->Keep my work-life balance as good as it is right now<!--]--></li><li><!--[-->Finish my thesis (finally - due time is March)<!--]--></li><li><!--[-->Improve my time management and planning skills<!--]--></li><!--]--></ul><!--]--></li><li><!--[-->Open Source
<ul><!--[--><li><!--[-->Invest time into open source and the community consistently (at least half a day a week, but probably more than that)<!--]--></li><!--]--></ul><!--]--></li><li><!--[-->Content Creation
<ul><!--[--><li><!--[-->Release at least 52 videos on YouTube - that's roughly once a week again<!--]--></li><li><!--[-->Become a YouTube partner<!--]--></li><li><!--[-->Write minimum 4 blog posts<!--]--></li><li><!--[-->Do a livestream on Twitch every quarter, at least.<!--]--></li><li><!--[--><del>Send out the first newsletter, then decide on a schedule (possibly 1x a quarter?)</del><!--]--></li><!--]--></ul><!--]--></li><li><!--[-->Speaking
<ul><!--[--><li><!--[-->Be a speaker at ~8 conferences or meetups - I want to continue speaking at conferences and meetups and share my knowledge with the community. No fixed number here, but I think 8 is a good baseline. I want to pick the conferences I speak at more carefully and focus on the ones that I really want to attend.<!--]--></li><!--]--></ul><!--]--></li><li><!--[-->Business
<ul><!--[--><li><!--[-->Continue to being able to pay my bills with my own company<!--]--></li><li><!--[-->Increase my companies profit by at least 10% compared to 2023 (no absolute numbers here, sorry folks!)<!--]--></li><!--]--></ul><!--]--></li><!--]--></ul><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->2023 was a terrific year for me, personally and business-wise - with lots of changes and new experiences. I'm really grateful for all the opportunities I had and the people I met. I'm looking forward to 2024 and hope to see you there! Some in person at conferences, some online on Twitter, YouTube, Twitch or somewhere else.<!--]--></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Integrate Sentry with your Nuxt 3 application - A Recipe]]></title>
            <link>https://www.lichter.io/articles/nuxt3-sentry-recipe/</link>
            <guid>https://www.lichter.io/articles/nuxt3-sentry-recipe/</guid>
            <pubDate>Wed, 27 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Sentry is a great tool to track errors and performance issues in your application - but the Nuxt module is not Nuxt 3 compatible yet. In this article, I'll show you how to integrate Sentry into your Nuxt 3 application, even before the module is ready and also share why it takes longer than you might think to build the module.]]></description>
            <content:encoded><![CDATA[<p><!--[-->Lots of people ask for certain Nuxt 3 compatible modules. Besides authentication, the most popular question revolves around the Sentry integration. A few weeks ago, <a><!--[--><!--[-->I announced working on a port for the Nuxt 2 module<!--]--><!--]--></a> - but this might take a little. In this article, I want to explain <em><!--[-->why<!--]--></em> it takes longer than writing an average module and also want to give you a simple <a><!--[--><!--[-->example recipe<!--]--><!--]--></a> for your own Nuxt 3 project, so you don't have to wait for the module.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->The burden of a general solution<!--]--></a><ul><!--[--><li><a><!--[-->Own your implementation<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Integrating Sentry into a project<!--]--></a><ul><!--[--><li><a><!--[-->Defining the runtimeConfig<!--]--></a><!----></li><li><a><!--[-->Nitro integration - Sentry on the server side<!--]--></a><!----></li><li><a><!--[-->Nuxt integration - Setting up the client side<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->The burden of a general solution<!--]--></a></h2><p><!--[-->If you want to integrate a solution into one single project, no matter if it is a feature, a third-party service, or a library, it is pretty straightforward. No matter how weird the requirements are, you can almost always find a way to make it work. It only has to work for you and your team - your project <strong><!--[-->only<!--]--></strong>.<!--]--></p><p><!--[-->The tricky part starts when you want to make it work for more people, dare I say, for everyone. It starts with thinking about all the different use cases, all the different setups, all the different requirements. You have to make sure that your solution is flexible enough to be used in all those different scenarios, or at least 80 percent of them. Also, your solution should not be too opinionated, so it can be used in a wide range of projects, no matter how weird the requirements are - maybe even weirder than in the project you've built the integration initially for.<!--]--></p><p><!--[-->And then it comes to configuration...<!--]--></p><p><!--[-->I personally am a big advocate of convention over configuration, but sometimes you just can't get around it. If you want to make your solution flexible enough, you have to provide a way to configure it. The options should be straightforward and easy to use, ideally aligning with the mental model of potential users already. You want to find the sweet spot between too many and too few options.<!--]--></p><p><!--[-->Frankly, this is a common struggle for module authors! Fellow Nuxt contributor <a><!--[--><!--[-->Julien Huang<!--]--><!--]--></a> (did I hear someone saying <em><!--[-->server components<!--]--></em>?) also shares similar experiences:<!--]--></p><blockquote><!--[--><p><!--[-->While developing the Nuxt Application Insights module, I had to split what I created as my own implementation into a Nitro package (nitro-applicationinsights) first and am now working on creating a Nuxt module.<!--]--></p><p><!--[-->It is kinda difficult to take out what specific implementation you've made for your own project and then decide, for example how to allow build time <strong><!--[-->and<!--]--></strong> runtime configuration so that the module or library is generic enough.<!--]--></p><p><!--[-->Thankfully, we have a nice hooking system for that 😝<!--]--></p><p><!--[-->Testing will also be quite complex as I have to dive into the source code to figure out what to mock.<!--]--></p><!--]--></blockquote><p><!--[-->In the case of the Sentry module, <a><!--[--><!--[-->Rafał<!--]--><!--]--></a>, the author and maintainer of the Nuxt 2 module did a great job and laid out a solid foundation - but this also adds another part of complexity: Which features and settings should be ported to the Nuxt 3 module? Which features and settings should be dropped, and what should added? And how to adapt the existing set of features to make it work seamlessly with the Composition API or Nitro, Nuxt's server engine?<!--]--></p><p><!--[-->I am afraid I can't answer these questions yet - but I am working on it. While being committed to finding the best possible solution, it will take some time. But just because the module needs a bit more time to become Nuxt 3 compatible, this doesn't mean <em><!--[-->you<!--]--></em> have to wait.<!--]--></p><h3><a><!--[-->Own your implementation<!--]--></a></h3><p><!--[-->And actually, it can be beneficial to own your implementation. It depends on the use case but can make sure that it fits your needs and requirements as needed and don't rely on module authors for a fix. Furthermore, you decide the scope of features and can omit unnecessary ones that might bloat your bundle, reducing complexity. In addition, you actually learn how the integration works under the hood, which means less unknown magic.<!--]--></p><p><!--[-->On the other hand, it might lead to increased maintenance effort as you are responsible for the implementation and have to keep it up to date. But this is a trade-off you have to make eventually, for every dependency, framework, library, or module you use.<!--]--></p><h2><a><!--[-->Integrating Sentry into a project<!--]--></a></h2><p><!--[-->Okay, enough of the theory and preamble. Let's get into integrating Sentry! The following approach was also implemented by me in one of my client's projects, which is <a><!--[--><!--[-->Intrinsify's<!--]--><!--]--></a> academic online portal.<!--]--></p><p><!--[-->We will take a look at both sides of your application, the server-side covered via integrating Sentry with Nitro, and the client side which will be covered through the Sentry Vue plugin.<!--]--></p><p><!--[-->To make things easier, I've created a tiny GitHub repository containing the code for this implementation, so you can easily move it into your own project. You can find <a><!--[--><!--[-->the GitHub repository here<!--]--><!--]--></a>.<!--]--></p><h3><a><!--[-->Defining the runtimeConfig<!--]--></a></h3><p><!--[-->Before we get into the details of each implementation, we need to define a common interface for configuration variables, which will be the </p><div><span></span><!--[-->runtimeConfig<!--]--><!----></div> of our Nuxt app!<!--]--><p></p><p><!--[-->There are lots of configuration options, but we start simple. We need Sentry's DSN to work and also provide an environment flag to later differentiate between development, staging, and production environments.<!--]--></p><p><!--[-->We have to put the content in the public part of the runtime config, as the Sentry Vue plugin will be used on the client side and needs access to the configuration.<!--]--></p><blockquote><!--[--><p><!--[-->By the way: If you want to learn more about how to properly set keys in your </p><div><span></span><!--[-->runtimeConfig<!--]--><!----></div>, Friday's launch week surprise will be really helpful for you!<!--]--><p></p><!--]--></blockquote><pre><!--[--><code><span><span>export</span><span> default</span><span> defineNuxtConfig</span><span>({
</span></span><span><span>  runtimeConfig</span><span>: {
</span></span><span><span>    public</span><span>: {
</span></span><span><span>      sentry</span><span>: {
</span></span><span><span>        dsn</span><span>: </span><span>''</span><span>,
</span></span><span><span>        environment</span><span>: </span><span>'</span><span>development</span><span>'</span><span>,
</span></span><span><span>      }
</span></span><span><span>    }
</span></span><span><span>  }
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->As mentioned before, various other configuration options can be part of your </p><div><span></span><!--[-->runtimeConfig<!--]--><!----></div> depending on your needs.
The best part is that we can now use environment variables to override the settings for different deploy:<!--]--><p></p><ul><!--[--><li><!--[--><div><span></span><!--[-->NUXT_PUBLIC_SENTRY_DSN<!--]--><!----></div> to set up Sentry's DSN<!--]--></li><li><!--[--><div><span></span><!--[-->NUXT_PUBLIC_SENTRY_ENVIRONMENT<!--]--><!----></div> to set up the correct environment tag<!--]--></li><li><!--[-->And whichever config options you want to add<!--]--></li><!--]--></ul><p><!--[-->Alright, we are good to go for starting with the server-side implementation!<!--]--></p><h3><a><!--[-->Nitro integration - Sentry on the server side<!--]--></a></h3><p><!--[-->When I originally started implementing the Nitro part of the Sentry integration, it was... tricky, to say the least.
But since Nitro v2.6, things got <em><!--[-->way<!--]--></em> easier thanks to the new hooks! We will use three of them straight away.
Also, this guide will work for a pure Nitro server too! Just skip the Nuxt-specific parts (e.g. ignore the </p><div><span></span><!--[-->server/<!--]--><!----></div> folder prefix) and you are good to go. Because Nitro also supports the runtime config and is the server engine for Nuxt 3, it will be really easy to adapt the code.<!--]--><p></p><p><!--[-->Before we start with using Nitro though, we need to install the dependencies, namely Sentry's node package and the profiling integration (if desired). This can be done via </p><div><span></span><!--[-->pnpm i -D @sentry/node @sentry/profiling-node<!--]--><!----></div>. Feel free to switch out the package manager to whatever you use.<!--]--><p></p><p><!--[-->After we do this, we will create a new Nitro plugin. In there, we want to initialize Sentry and set up the profiling integration. We will also retrieve our variables from the runtime config and set up the nitro hook:<!--]--></p><pre><!--[--><code><span><span>import</span><span> *</span><span> as</span><span> Sentry</span><span> from</span><span> '</span><span>@sentry/node</span><span>'
</span></span><span><span>import</span><span> {</span><span> nodeProfilingIntegration</span><span> }</span><span> from</span><span> '</span><span>@sentry/profiling-node</span><span>'
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> defineNitroPlugin</span><span>((</span><span>nitroApp</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  const </span><span>{</span><span> public</span><span>:</span><span> {</span><span> sentry</span><span> }</span><span> }</span><span> =</span><span> useRuntimeConfig</span><span>()
</span></span><span><span>
</span></span><span><span>  // If no sentry DSN set, ignore and warn in the console
</span></span><span><span>  if</span><span> (</span><span>!</span><span>sentry</span><span>.</span><span>dsn</span><span>)</span><span> {
</span></span><span><span>    console</span><span>.</span><span>warn</span><span>(</span><span>'</span><span>Sentry DSN not set, skipping Sentry initialization</span><span>'</span><span>)
</span></span><span><span>    return
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  // Initialize Sentry
</span></span><span><span>  Sentry</span><span>.</span><span>init</span><span>({
</span></span><span><span>    dsn</span><span>: </span><span>sentry</span><span>.</span><span>dsn</span><span>,
</span></span><span><span>    environment</span><span>: </span><span>sentry</span><span>.</span><span>environment</span><span>,
</span></span><span><span>    integrations</span><span>: [</span><span>nodeProfilingIntegration</span><span>()],
</span></span><span><span>    // Performance Monitoring
</span></span><span><span>    tracesSampleRate</span><span>: </span><span>1.0</span><span>, </span><span>// Change in production!
</span></span><span><span>    // Set sampling rate for profiling - this is relative to tracesSampleRate
</span></span><span><span>    profilesSampleRate</span><span>: </span><span>1.0</span><span> // Change in production!
</span></span><span><span>  })
</span></span><span><span>
</span></span><span><span>  // Here comes the hooks
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->So far so good, we set up the configuration. As mentioned before, lots of things can be added to the </p><div><span></span><!--[-->runtimeConfig<!--]--><!----></div>, such as the sample rates. We could also switch them based on <div><span></span><!--[-->sentry.environment<!--]--><!----></div> though. And now let's jump into how to use the hooks.<!--]--><p></p><h4><a><!--[-->Capturing errors with Sentry in Nitro<!--]--></a></h4><p><!--[-->First, we want to ensure that Sentry will be capturing all kinds of errors Nitro is throwing at <del>us</del> the user. For this, we can use the </p><div><span></span><!--[-->nitroApp<!--]--><!----></div> param from the plugin function, together with the <div><span></span><!--[-->error<!--]--><!----></div> hook which will be called when an unhandled error is thrown:<!--]--><p></p><pre><!--[--><code><span><span>// Inside the plugin, after initializing sentry
</span></span><span><span>nitroApp</span><span>.</span><span>hooks</span><span>.</span><span>hook</span><span>(</span><span>'</span><span>error</span><span>'</span><span>,</span><span> (</span><span>error</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  Sentry</span><span>.</span><span>captureException</span><span>(</span><span>error</span><span>)
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->Now, <strong><!--[-->all errors<!--]--></strong> will be captured. You might not want to capture some of these, e.g. 404s or 422s, as they are usually not relevant for error tracking. This can be done with a bit of custom logic - by checking if the error is an H3Error, and if so, if the status code is one of the exceptions we don't want to track:<!--]--></p><pre><!--[--><code><span><span>// On the top of the file, import H3Error!
</span></span><span><span>import</span><span> {</span><span> H3Error</span><span> }</span><span> from</span><span> '</span><span>h3</span><span>'
</span></span><span><span>
</span></span><span><span>// Inside the plugin, after initializing sentry
</span></span><span><span>nitroApp</span><span>.</span><span>hooks</span><span>.</span><span>hook</span><span>(</span><span>'</span><span>error</span><span>'</span><span>,</span><span> (</span><span>error</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  // Do not handle 404s and 422s
</span></span><span><span>  if</span><span> (</span><span>error</span><span> instanceof</span><span> H3Error</span><span>)</span><span> {
</span></span><span><span>    if</span><span> (</span><span>error</span><span>.</span><span>statusCode</span><span> ===</span><span> 404</span><span> ||</span><span> error</span><span>.</span><span>statusCode</span><span> ===</span><span> 422</span><span>)</span><span> {
</span></span><span><span>      return
</span></span><span><span>    }
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  Sentry</span><span>.</span><span>captureException</span><span>(</span><span>error</span><span>)
</span></span><span><span>})
</span></span></code><!--]--></pre><h4><a><!--[-->Sharing Sentry with the event context<!--]--></a></h4><p><!--[-->After the initial setup is done, we also want to ensure that we can use Sentry in our API routes, e.g. to attach a user, send messages, and whatever your use case is. The best way is attaching Sentry to the event context, so we can access it from any event handler. We can do this <em><!--[-->for every request<!--]--></em> by using the </p><div><span></span><!--[-->request<!--]--><!----></div> hook! This hook will be called for every request, so we can attach Sentry to the event context in just four (soon three) lines.
There is a type issue in Nitro at the time of writing, but I'm confident it won't stay for long. Until then, we need to use a <div><span></span><!--[-->@ts-ignore<!--]--><!----></div>, or better <div><span></span><!--[-->@ts-expect-error<!--]--><!----></div> with a comment!<!--]--><p></p><pre><!--[--><code><span><span>nitroApp</span><span>.</span><span>hooks</span><span>.</span><span>hook</span><span>(</span><span>'</span><span>request</span><span>'</span><span>,</span><span> (</span><span>event</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  event</span><span>.</span><span>context</span><span>.</span><span>$sentry</span><span> =</span><span> Sentry
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->Okay, we are not <strong><!--[-->fully done yet<!--]--></strong> if we use TypeScript because the event context doesn't know about the </p><div><span></span><!--[-->$sentry<!--]--><!----></div> property yet. We can fix this by augmenting the EventContext type in a <div><span></span><!--[-->.d.ts<!--]--><!----></div> file in your project root:<!--]--><p></p><pre><!--[--><code><span><span>import</span><span> type</span><span> {</span><span> Sentry</span><span> }</span><span> from</span><span> '</span><span>@sentry/node</span><span>'
</span></span><span><span>
</span></span><span><span>declare</span><span> module</span><span> '</span><span>h3</span><span>'</span><span> {
</span></span><span><span>  interface</span><span> H3EventContext</span><span> {
</span></span><span><span>    $sentry</span><span>?</span><span>: </span><span>Sentry
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->It might be a bit confusing that you have to augment the </p><div><span></span><!--[-->H3EventContext<!--]--><!----></div> but it makes total sense: Nitro is using H3 under the hood, using its event structure and context. So we have to augment the H3 event context, which will then be used by Nitro and our event handlers.<!--]--><p></p><p><!--[-->Now we can do something like this in any event handler:<!--]--></p><pre><!--[--><code><span><span>export</span><span> default</span><span> defineApiHandler</span><span>(</span><span>async</span><span> (</span><span>event</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  const </span><span>sentry</span><span> =</span><span> event</span><span>.</span><span>context</span><span>.</span><span>$sentry
</span></span><span><span>  if</span><span>(</span><span>sentry</span><span>)</span><span> {
</span></span><span><span>    // Do something with Sentry if exists
</span></span><span><span>    // e.g. 
</span></span><span><span>    sentry</span><span>.</span><span>setUser</span><span>({</span><span>/*...*/</span><span>})
</span></span><span><span>  }
</span></span><span><span>})
</span></span></code><!--]--></pre><h4><a><!--[-->Closing Sentry on shutdown<!--]--></a></h4><p><!--[-->An often forgotten task is cleaning up! We want to ensure that Sentry is gracefully shut down when the Nitro server stops.
We can achieve this by using the </p><div><span></span><!--[-->close<!--]--><!----></div> hook:<!--]--><p></p><pre><!--[--><code><span><span>nitroApp</span><span>.</span><span>hooks</span><span>.</span><span>hookOnce</span><span>(</span><span>'</span><span>close</span><span>'</span><span>,</span><span> async</span><span> ()</span><span> =&gt;</span><span> {
</span></span><span><span>  await</span><span> Sentry</span><span>.</span><span>close</span><span>(</span><span>2000</span><span>)
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->And that's it! Our nitro integration is ready. Now, up to the client side.<!--]--></p><h3><a><!--[-->Nuxt integration - Setting up the client side<!--]--></a></h3><p><!--[-->The client side is also not that complicated. Technically, we have to do what we did in the Nitro plugin, but on the client side. We will use a client-only Nuxt plugin for this. In there, we also initialize Sentry based on our </p><div><span></span><!--[-->runtimeConfig<!--]--><!----></div> values and inject it into the app.<!--]--><p></p><p><!--[-->Let's not forget to add the Sentry vue plugin to our dependencies before via </p><div><span></span><!--[-->pnpm i -D @sentry/vue<!--]--><!----></div>.<!--]--><p></p><p><!--[-->Then, we provide the vue app via </p><div><span></span><!--[-->nuxtApp.vueApp<!--]--><!----></div> and the router via the <div><span></span><!--[-->useRouter()<!--]--><!----></div> composable. Also, we use the <div><span></span><!--[-->dsn<!--]--><!----></div> and <div><span></span><!--[-->environment<!--]--><!----></div> keys through the <div><span></span><!--[-->useRuntimeConfig<!--]--><!----></div> composable.<!--]--><p></p><pre><!--[--><code><span><span>import</span><span> *</span><span> as</span><span> Sentry</span><span> from</span><span> '</span><span>@sentry/vue</span><span>'
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> defineNuxtPlugin</span><span>((</span><span>nuxtApp</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  const </span><span>router</span><span> =</span><span> useRouter</span><span>()
</span></span><span><span>  const </span><span>{</span><span> public</span><span>:</span><span> {</span><span> sentry</span><span> }</span><span> }</span><span> =</span><span> useRuntimeConfig</span><span>()
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>!</span><span>sentry</span><span>.</span><span>dsn</span><span>)</span><span> {
</span></span><span><span>    return
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  Sentry</span><span>.</span><span>init</span><span>({
</span></span><span><span>    app</span><span>: </span><span>nuxtApp</span><span>.</span><span>vueApp</span><span>,
</span></span><span><span>    dsn</span><span>: </span><span>sentry</span><span>.</span><span>dsn</span><span>,
</span></span><span><span>    environment</span><span>: </span><span>sentry</span><span>.</span><span>environment</span><span>,
</span></span><span><span>    integrations</span><span>: [
</span></span><span><span>      Sentry</span><span>.</span><span>browserTracingIntegration</span><span>({ </span><span>router</span><span> }), 
</span></span><span><span>      Sentry</span><span>.</span><span>replayIntegration</span><span>({
</span></span><span><span>        maskAllText</span><span>: </span><span>false</span><span>,
</span></span><span><span>        blockAllMedia</span><span>: </span><span>false</span><span>,
</span></span><span><span>      }),
</span></span><span><span>    ],
</span></span><span><span>
</span></span><span><span>    // Configure this whole part as you need it!
</span></span><span><span>
</span></span><span><span>    tracesSampleRate</span><span>: </span><span>0.2</span><span>, </span><span>// Change in prod
</span></span><span><span>
</span></span><span><span>    // Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled
</span></span><span><span>    tracePropagationTargets</span><span>: [</span><span>'</span><span>localhost</span><span>'</span><span>, </span><span>'</span><span>https://your-server.com</span><span>'</span><span>],
</span></span><span><span>
</span></span><span><span>    replaysSessionSampleRate</span><span>: </span><span>1.0</span><span>, </span><span>// Change in prod
</span></span><span><span>    replaysOnErrorSampleRate</span><span>: </span><span>1.0</span><span>, </span><span>// Change in prod if necessary
</span></span><span><span>  })
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->From here, error tracking works out of the box. Of course, you can build your own composable exposing </p><div><span></span><!--[-->Sentry<!--]--><!----></div> helpers or import it in the components as needed - but that's up to you now as you own the implementation!<!--]--><p></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->We did it! Together, we set up a very simple Sentry integration for our Nuxt 3 project, only by relying on Nuxt, Nitro, and Sentry.
Don't forget to check out the <a><!--[--><!--[-->full result on GitHub<!--]--><!--]--></a>.<!--]--></p><p><!--[-->I hope this article helped you understand why module development can be tricky, might take a little longer than just adding your own implementation, and what pros and cons owning an implementation brings with it.
Also, I hope now your Sentry setup is up and running.<!--]--></p><p><!--[-->If you have any questions, feel free to reach out to me as usual! And please don't forget to share this article with your friends and colleagues if you found it helpful.<!--]--></p><p><!--[-->Happy hacking!<!--]--></p><style>html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sxA9i, html code.shiki .sxA9i{--shiki-default:#4C9A91}html pre.shiki code .syEag, html code.shiki .syEag{--shiki-default:#5DA994}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Dynamically enable SSR or SPA mode in a Nuxt 3 app]]></title>
            <link>https://www.lichter.io/articles/nuxt3-dynamic-ssr-spa/</link>
            <guid>https://www.lichter.io/articles/nuxt3-dynamic-ssr-spa/</guid>
            <pubDate>Tue, 26 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[Server Side Rendering does have some limitations, such as the inability to access platform-based APIs like local storage on the server side. However, what if you could activate SSR exclusively for pages that need SEO and employ the classic single-page application (SPA) mode for other pages? Discover how to achieve this in the following article.]]></description>
            <content:encoded><![CDATA[<blockquote><!--[--><p><!--[-->This is the second post on my new blog and it feels <strong><!--[-->so good to be back at writing<!--]--></strong>! Thanks a lot for all the positive feedback on my <a><!--[--><!--[-->previous article<!--]--><!--]--></a>. Also, don't forget to join the big open source launch party this Thursday (Sept 28th, 2023) on my <a><!--[--><!--[-->Twitch channel<!--]--><!--]--></a>, starting 15:30! But now back to the topic!<!--]--></p><!--]--></blockquote><p><!--[-->In Nuxt 2, you could already selectively enable SSR or SPA mode based on the page URL <a><!--[--><!--[-->with a little trick<!--]--><!--]--></a>. However, this approach doesn't work anymore in Nuxt 3. But don't worry, in this article, we will cover three different ways to selectively switch SSR on or off for your site!<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Prelude - The &lt;ClientOnly&gt; component<!--]--></a><!----></li><li><a><!--[-->Solution 1 - routeRules<!--]--></a><!----></li><li><a><!--[-->Solution 2 - The experimental x-nuxt-no-ssr header<!--]--></a><!----></li><li><a><!--[-->Solution 3 - A custom Nitro middleware<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Prelude - The &lt;ClientOnly&gt; component<!--]--></a></h2><p><!--[-->A common solution to render <em><!--[-->parts<!--]--></em> of your application only on the client is the <a><!--[--><!--[--></a></p><div><a><span></span><!--[-->&lt;ClientOnly&gt;<!--]--><!----></a></div><a> component<!--]--><!--]--></a>. It is provided by default through Nuxt and can be used as a wrapper component around the content you want to render only on the client side. The following example shows how to use it:<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>ClientOnly</span><span>&gt;
</span></span><span><span>  This content will not be rendered on the server side
</span></span><span><span>&lt;/</span><span>ClientOnly</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->You can even set a few options on the component to control the behavior, such as the content for the server-side fallback, either via props (</p><div><span></span><!--[-->fallback-tag<!--]--><!----></div> for the tag and <div><span></span><!--[-->fallback<!--]--><!----></div> for content) or via template:<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>ClientOnly</span><span>&gt;
</span></span><span><span>  This content will not be rendered on the server side
</span></span><span><span>  &lt;template #fallback&gt;
</span></span><span><span>    This content will be rendered on the server side, ideally this should be some loading state
</span></span><span><span>  &lt;/template&gt;
</span></span><span><span>&lt;/</span><span>ClientOnly</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->While using the </p><div><span></span><!--[-->&lt;ClientOnly&gt;<!--]--><!----></div> component is a great solution for rendering parts of your application only on the client side, it can be applied only at the component level. Of course, now one could start wrapping the <div><span></span><!--[-->&lt;NuxtPage&gt;<!--]--><!----></div> component in their <div><span></span><!--[-->app.vue<!--]--><!----></div>, which is actually a valid pattern if done for all pages, but not a proper solution when you only want to selectively enable or disable SSR for a few pages. Let's have a look at some solutions now!<!--]--><p></p><h2><a><!--[-->Solution 1 - <div><span></span><!--[-->routeRules<!--]--><!----></div><!--]--></a></h2><p><!--[-->Since the introduction of <a><!--[--><!--[--></a></p><div><a><span></span><!--[-->routeRoules<!--]--><!----></a></div><!--]--><!--]-->, handling the rendering mode for a specific route is as easy as adding a <div><span></span><!--[-->routeRule<!--]--><!----></div> to your <div><span></span><!--[-->nuxt.config.ts<!--]--><!----></div>:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> defineNuxtConfig</span><span>({
</span></span><span><span>  routeRules</span><span>: {
</span></span><span><span>    '</span><span>/spa-route-rule</span><span>'</span><span>: { </span><span>ssr</span><span>: </span><span>false</span><span> }
</span></span><span><span>  }
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->This also works for all routes that match a certain pattern, using a wildcard. Then you also have to add the index URL manually though:<!--]--></p><pre><!--[--><code><span><span>export</span><span> default</span><span> defineNuxtConfig</span><span>({
</span></span><span><span>  routeRules</span><span>: {
</span></span><span><span>    '</span><span>/spa-route-rule</span><span>'</span><span>: { </span><span>ssr</span><span>: </span><span>false</span><span> },
</span></span><span><span>    '</span><span>/spa-route-rule/**</span><span>'</span><span>: { </span><span>ssr</span><span>: </span><span>false</span><span> }
</span></span><span><span>  }
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->This is the recommended approach. But sometimes, just choosing the route might not be enough!<!--]--></p><h2><a><!--[-->Solution 2 - The experimental <div><span></span><!--[-->x-nuxt-no-ssr<!--]--><!----></div> header<!--]--></a></h2><p><!--[-->Another way to disable SSR, this time for the whole request and <strong><!--[-->not bound to a path<!--]--></strong>, is to set the </p><div><span></span><!--[-->x-nuxt-no-ssr<!--]--><!----></div> header to a truthy value, like <div><span></span><!--[-->true<!--]--><!----></div> or <div><span></span><!--[-->1<!--]--><!----></div> when opening the page. If you try this with a default Nuxt application, nothing will happen. This is because the header is not handled by Nuxt by default. However, you can enable it by setting the <div><span></span><!--[-->experimental.respectNoSSRHeader<!--]--><!----></div> option to <div><span></span><!--[-->true<!--]--><!----></div> in your <div><span></span><!--[-->nuxt.config.ts<!--]--><!----></div>:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> defineNuxtConfig</span><span>({
</span></span><span><span>  experimental</span><span>: {
</span></span><span><span>    respectNoSSRHeader</span><span>: </span><span>true</span><span>,
</span></span><span><span>  },
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->Using the header is a rather uncommon solution, but it might be useful for debugging a staging environment. Also, be aware that this is an <em><!--[-->experimental feature<!--]--></em> and could be adapted/changed in the future without a major version bump.
As a last note: You might not want to enable this feature in production, as it could be used to disable SSR for all pages.<!--]--></p><h2><a><!--[-->Solution 3 - A custom Nitro middleware<!--]--></a></h2><p><!--[-->If you really want some <em><!--[-->fine-grained<!--]--></em> control over the SSR/SPA mode, you can always write your own Nitro middleware, very similar to the <a><!--[--><!--[--></a></p><div><a><span></span><!--[-->serverMiddleware<!--]--><!----></a></div><a> approach back in Nuxt 2<!--]--><!--]--></a>.
This is also the most flexible solution, as you can implement any logic you want. The following example shows how to disable SSR for a path including <div><span></span><!--[-->/spa-header-custom<!--]--><!----></div>:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> defineEventHandler</span><span>((</span><span>event</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>event</span><span>.</span><span>path</span><span>.</span><span>includes</span><span>(</span><span>'</span><span>/spa-header-custom</span><span>'</span><span>))</span><span> {
</span></span><span><span>    return
</span></span><span><span>  }
</span></span><span><span>  event</span><span>.</span><span>context</span><span>.</span><span>nuxt</span><span> =</span><span> event</span><span>.</span><span>context</span><span>.</span><span>nuxt</span><span> ||</span><span> {}
</span></span><span><span>  event</span><span>.</span><span>context</span><span>.</span><span>nuxt</span><span>.</span><span>noSSR</span><span> =</span><span> true
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->Be aware that this approach relies on internals, though breaking changes are unlikely to happen here. Also, the </p><div><span></span><!--[-->x-nuxt-no-ssr<!--]--><!----></div> header is <a><!--[--><!--[-->implemented the same way internally<!--]--><!--]--></a>.<!--]--><p></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->There are many ways to selectively enable or disable SSR in your Nuxt 3 application. And you can see them all in use <a><!--[--><!--[-->in this StackBlitz<!--]--><!--]--></a>.<!--]--></p><p><!--[-->The recommended way is to use </p><div><span></span><!--[-->routeRules<!--]--><!----></div>, as it is the most straightforward and future-proof solution. However, if you need more control, you can always use the <div><span></span><!--[-->x-nuxt-no-ssr<!--]--><!----></div> header or write your own Nitro middleware.<!--]--><p></p><p><!--[-->I hope this article was helpful to you. If you have any questions, feel free to reach out to me on any of the social networks below. And as usual, please share this article with your friends and colleagues if you liked it. Happy hacking!<!--]--></p><style>html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .sNpkn, html code.shiki .sNpkn{--shiki-default:#DBD7CAEE}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vue 3: How to load dynamic images]]></title>
            <link>https://www.lichter.io/articles/nuxt3-vue3-dynamic-images/</link>
            <guid>https://www.lichter.io/articles/nuxt3-vue3-dynamic-images/</guid>
            <pubDate>Mon, 25 Sep 2023 00:00:00 GMT</pubDate>
            <description><![CDATA[With the release of Vue 3, a lot of things became easier. Unfortunately, loading dynamic assets such as images is not one of them. In the following article, I want to demystify the process of dynamic asset loading in Vue 3 or Nuxt 3 and explain why static assets can be loaded easily...]]></description>
            <content:encoded><![CDATA[<blockquote><!--[--><p><!--[-->This article is written for <strong><!--[-->Vue 3<!--]--></strong> and <strong><!--[-->Nuxt 3<!--]--></strong>.<!--]--></p><p><!--[-->If you are looking for the <strong><!--[-->Nuxt 2<!--]--></strong>/<strong><!--[-->Vue 2<!--]--></strong> version of this article, please <a><!--[--><!--[-->follow this link to the older Nuxt 2 / Vue 2 version<!--]--><!--]--></a>.<!--]--></p><!--]--></blockquote><p><!--[-->When using Vue or Nuxt, we have are spoilt for choice when it comes to asset handling: Do we put them in the </p><div><span></span><!--[-->assets<!--]--><!----></div> folder or should we rather utilize the <div><span></span><!--[-->public<!--]--><!----></div> folder? Depending on the purpose and requirements for the image, this decision can be the one or the other. In this article, we focus on one specific use case though: What if we want to load images (or other assets) <em><!--[-->dynamically<!--]--></em>?<!--]--><p></p><p><!--[-->Read further to find out why loading static assets from neither folder is no problem at all and which pattern to use when the asset path or name must be dynamic.
In case you want to skip the internals and explanations, you can do so and go straight to <a><!--[--><!--[-->the solution<!--]--><!--]--></a>, but you will miss out some in-depth information!<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Static images<!--]--></a><!----></li><li><a><!--[-->The public folder strategy<!--]--></a><!----></li><li><a><!--[-->The assets folder strategy<!--]--></a><ul><!--[--><li><a><!--[-->Naive approach<!--]--></a><!----></li><li><a><!--[-->Bundler magic<!--]--></a><!----></li><li><a><!--[-->The import.meta.glob trick<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->The solution<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Static images<!--]--></a></h2><p><!--[-->Let's define our experimental use case first: Imagine a component called </p><div><span></span><!--[-->Doggo.vue<!--]--><!----></div> which should display the image of a cute puppy.
I mean, we <em><!--[-->all love puppies<!--]--></em>, don’t we 🐕️?<!--]--><p></p><p><!--[-->The only thing our components needs is a template with a single image tag using the desired path as </p><div><span></span><!--[-->src<!--]--><!----></div> attribute.<!--]--><p></p><p><!--[-->When using the </p><div><span></span><!--[-->assets<!--]--><!----></div> folder, you can either use relative paths or an alias like <div><span></span><!--[-->@<!--]--><!----></div> or <div><span></span><!--[-->~<!--]--><!----></div>, which comes pre-configured in Nuxt:<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> src</span><span>=</span><span>"</span><span>@/assets/doggos/riley.jpg</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> src</span><span>=</span><span>"</span><span>../assets/doggos/riley.jpg</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->In case you are using the </p><div><span></span><!--[-->public<!--]--><!----></div> folder, the files will be mapped to your domain eventually, so you can omit the <div><span></span><!--[-->public<!--]--><!----></div>:<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> src</span><span>=</span><span>"</span><span>/doggos/riley.jpg</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->So far so good -- but what if we have a <em><!--[-->list of cute puppies<!--]--></em> and the user can <strong><!--[-->decide which image to display on the page<!--]--></strong>?<!--]--></p><h2><a><!--[-->The <div><span></span><!--[-->public<!--]--><!----></div> folder strategy<!--]--></a></h2><p><!--[-->One suitable way is to put all the images in the </p><div><span></span><!--[-->public<!--]--><!----></div> folder and then refer to them via a computed property.<!--]--><p></p><p><!--[-->Let's say we load the dynamic image source via Vue’s binding system. Imagine we have a small component set up where we can select a puppy:<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>script</span><span> setup</span><span> lang</span><span>=</span><span>"</span><span>ts</span><span>"</span><span>&gt;
</span></span><span><span>const </span><span>dogNames</span><span> =</span><span> [</span><span>'</span><span>Riley</span><span>'</span><span>,</span><span> '</span><span>Annie</span><span>'</span><span>,</span><span> '</span><span>Marvin</span><span>'</span><span>];
</span></span><span><span>const </span><span>selectedDog</span><span> =</span><span> ref</span><span>(</span><span>''</span><span>);
</span></span><span><span>&lt;/</span><span>script</span><span>&gt;
</span></span><span><span>
</span></span><span><span>&lt;</span><span>template</span><span>&gt;
</span></span><span><span>  &lt;</span><span>div</span><span>&gt;
</span></span><span><span>    &lt;</span><span>label</span><span> v-for</span><span>=</span><span>"</span><span>doggo in dogNames</span><span>"</span><span> :key</span><span>=</span><span>"</span><span>doggo</span><span>"</span><span> style</span><span>=</span><span>"</span><span>margin-right: 2rem</span><span>"</span><span>&gt;
</span></span><span><span>      &lt;</span><span>input</span><span> type</span><span>=</span><span>"</span><span>radio</span><span>"</span><span> :value</span><span>=</span><span>"</span><span>doggo</span><span>"</span><span> v-model</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span> /&gt;
</span></span><span><span>      {{ doggo }}
</span></span><span><span>    &lt;/</span><span>label</span><span>&gt;
</span></span><span><span>    &lt;</span><span>img</span><span> :src</span><span>=</span><span>"</span><span>`/doggos/${selectedDog.toLowerCase()}.jpg`</span><span>"</span><span> width</span><span>=</span><span>"</span><span>500</span><span>"</span><span> :alt</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span> /&gt;
</span></span><span><span>  &lt;/</span><span>div</span><span>&gt;
</span></span><span><span>&lt;/</span><span>template</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->All that is left to do is to retrieve the correct image for the </p><div><span></span><!--[-->selectedDog<!--]--><!----></div>.<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> :</span><span>src</span><span>=</span><span>"</span><span>`</span><span>/doggos/</span><span>${</span><span>selectedDog</span><span>.</span><span>toLowerCase</span><span>()</span><span>}</span><span>.jpg</span><span>`</span><span>"</span><span> :</span><span>alt</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->And it works! We can now select a puppy and the image will be displayed. Open <a><!--[--><!--[-->this CodeSandbox<!--]--><!--]--></a> to see the code up and running.<!--]--></p><p><!--[-->This approach has a few downsides though:<!--]--></p><ul><!--[--><li><!--[-->Caching: The image will be cached by the browser, so if we want to change the image, we have to change the file name as well. This is not optimal, especially if we want to use the same image in multiple places.<!--]--></li><li><!--[-->Image optimization: The image will not be optimized by Vite plugins or similar, so we have to do it manually.<!--]--></li><li><!--[-->Image size: The image will be loaded in its original size, which can be a problem for mobile users with a slow connection if the image is big.<!--]--></li><!--]--></ul><p><!--[-->Let's take a look how the approach for the </p><div><span></span><!--[-->assets<!--]--><!----></div> folder looks like:<!--]--><p></p><h2><a><!--[-->The <div><span></span><!--[-->assets<!--]--><!----></div> folder strategy<!--]--></a></h2><p><!--[-->Alright, let's take the code from above as base. As a naive approach, why not just replace the paths using the path to assets instead?<!--]--></p><h3><a><!--[-->Naive approach<!--]--></a></h3><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> :</span><span>src</span><span>=</span><span>"</span><span>`</span><span>../assets/doggos/</span><span>${</span><span>selectedDog</span><span>.</span><span>toLowerCase</span><span>()</span><span>}</span><span>.jpg</span><span>`</span><span>"</span><span> :</span><span>alt</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->Let’s add that line quickly and see what happens when we push the button mapped to Riley…<!--]--></p><p><!--[--><strong><!--[-->Bummer, a broken image<!--]--></strong> and only the alt tag! Let us take a look at the DOM. It contains the following image tag:<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> src</span><span>=</span><span>"</span><span>../assets/doggos/riley.jpg</span><span>"</span><span> alt</span><span>=</span><span>"</span><span>Riley</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->It means that the <strong><!--[-->asset path hasn’t been replaced<!--]--></strong>. It is the string that the expression in our template string above evaluates to, but no bundler magic happens.<!--]--></p><p><!--[-->So, what now?<!--]--></p><h3><a><!--[-->Bundler magic<!--]--></a></h3><p><!--[-->The solution to this problem depends on the bundler you are using. If you are using Webpack with Vue 3, which is rather uncommon, you can follow the solution from the <a><!--[--><!--[-->Vue 2 / Nuxt 2 post<!--]--><!--]--></a>.<!--]--></p><p><!--[-->We will focus on Vite here, as it is the default bundler for Vue 3 and Nuxt 3.<!--]--></p><p><!--[-->As Vite does not support </p><div><span></span><!--[-->require<!--]--><!----></div> as you might be used to if you've used webpack before, a "simple" solution with <div><span></span><!--[-->require<!--]--><!----></div> is not possible.
We have to use a different approach.<!--]--><p></p><h3><a><!--[-->The <div><span></span><!--[-->import.meta.glob<!--]--><!----></div> trick<!--]--></a></h3><p><!--[-->Instead of </p><div><span></span><!--[-->require<!--]--><!----></div>, we can use <a><!--[--><!--[--><div><span></span><!--[-->import.meta.glob<!--]--><!----></div><!--]--><!--]--></a>. It is a special vite function that allows us to import multiple files at once. We can use it to import all images from a folder and then use the image name as key to access the image.<!--]--><p></p><p><!--[-->Let's start simple and grab all </p><div><span></span><!--[-->.jpg<!--]--><!----></div> files from the <div><span></span><!--[-->@/assets/doggos<!--]--><!----></div> folder, where our images are located.
It is very important to be as strict as possible, otherwise you can end up with a lot of files you don't want to import, harming performance of the application.<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>script</span><span> setup</span><span> lang</span><span>=</span><span>"</span><span>ts</span><span>"</span><span>&gt;
</span></span><span><span>
</span></span><span><span>const </span><span>glob</span><span> =</span><span> import</span><span>.</span><span>meta</span><span>.</span><span>glob</span><span>(</span><span>'</span><span>@/assets/doggos/*.jpg</span><span>'</span><span>,</span><span> { </span><span>eager</span><span>: </span><span>true</span><span> })
</span></span><span><span>&lt;/</span><span>script</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->If we stringify the result, we can see that we get an object with the local image path as key and the image path in a nested object as value.
The </p><div><span></span><!--[-->default<!--]--><!----></div> key exists, because <div><span></span><!--[-->import.meta.glob<!--]--><!----></div> is used for module imports of any kind.<!--]--><p></p><pre><!--[--><code><span><span>{
</span></span><span><span>  "</span><span>/assets/doggos/annie.jpg</span><span>"</span><span>:</span><span> {
</span></span><span><span>    "</span><span>default</span><span>"</span><span>:</span><span> "</span><span>/_nuxt/assets/doggos/annie.jpg</span><span>"
</span></span><span><span>  },
</span></span><span><span>  "</span><span>/assets/doggos/marvin.jpg</span><span>"</span><span>:</span><span> {
</span></span><span><span>    "</span><span>default</span><span>"</span><span>:</span><span> "</span><span>/_nuxt/assets/doggos/marvin.jpg</span><span>"
</span></span><span><span>  },
</span></span><span><span>  "</span><span>/assets/doggos/riley.jpg</span><span>"</span><span>:</span><span> {
</span></span><span><span>    "</span><span>default</span><span>"</span><span>:</span><span> "</span><span>/_nuxt/assets/doggos/riley.jpg</span><span>"
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->This is close to what we want, so we need to do some transformations.
We will take the entries of the object and map over them, eventually assembling them into an object again.
In the </p><div><span></span><!--[-->map<!--]--><!----></div> function, we ensure to get rid of the nested object, to create a <div><span></span><!--[-->{ filename: path }<!--]--><!----></div> structure.
to extract the filename from the path, we can use the <div><span></span><!--[-->filename<!--]--><!----></div> function from <a><!--[--><!--[--><div><span></span><!--[-->pathe<!--]--><!----></div>'s utils<!--]--><!--]--></a>.<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>script</span><span> setup</span><span> lang</span><span>=</span><span>"</span><span>ts</span><span>"</span><span>&gt;
</span></span><span><span>import</span><span> {</span><span> filename</span><span> }</span><span> from</span><span> '</span><span>pathe/utils</span><span>'
</span></span><span><span>
</span></span><span><span>const </span><span>glob</span><span> =</span><span> import</span><span>.</span><span>meta</span><span>.</span><span>glob</span><span>(</span><span>'</span><span>@/assets/doggos/*.jpg</span><span>'</span><span>,</span><span> { </span><span>eager</span><span>: </span><span>true</span><span> })
</span></span><span><span>const </span><span>images</span><span> =</span><span> Object</span><span>.</span><span>fromEntries</span><span>(
</span></span><span><span>  Object</span><span>.</span><span>entries</span><span>(</span><span>glob</span><span>).</span><span>map</span><span>(([</span><span>key</span><span>,</span><span> value</span><span>])</span><span> =&gt;</span><span> [</span><span>filename</span><span>(</span><span>key</span><span>),</span><span> value</span><span>.</span><span>default</span><span>])
</span></span><span><span>)
</span></span><span><span>&lt;/</span><span>script</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->This is also the <a><!--[--><!--[-->suggested workaround<!--]--><!--]--></a> from Daniel Roe for achieving a "require-like" behavior with Vite.<!--]--></p><p><!--[-->Now, let's put it all together!<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>script</span><span> setup</span><span> lang</span><span>=</span><span>"</span><span>ts</span><span>"</span><span>&gt;
</span></span><span><span>import</span><span> {</span><span> filename</span><span> }</span><span> from</span><span> '</span><span>pathe/utils</span><span>'
</span></span><span><span>const </span><span>dogNames</span><span> =</span><span> [</span><span>'</span><span>Riley</span><span>'</span><span>,</span><span> '</span><span>Annie</span><span>'</span><span>,</span><span> '</span><span>Marvin</span><span>'</span><span>];
</span></span><span><span>const </span><span>selectedDog</span><span> =</span><span> ref</span><span>(</span><span>''</span><span>);
</span></span><span><span>
</span></span><span><span>const </span><span>glob</span><span> =</span><span> import</span><span>.</span><span>meta</span><span>.</span><span>glob</span><span>(</span><span>'</span><span>@/assets/doggos/*.jpg</span><span>'</span><span>,</span><span> { </span><span>eager</span><span>: </span><span>true</span><span> })
</span></span><span><span>const </span><span>images</span><span> =</span><span> Object</span><span>.</span><span>fromEntries</span><span>(
</span></span><span><span>  Object</span><span>.</span><span>entries</span><span>(</span><span>glob</span><span>).</span><span>map</span><span>(([</span><span>key</span><span>,</span><span> value</span><span>])</span><span> =&gt;</span><span> [</span><span>filename</span><span>(</span><span>key</span><span>),</span><span> value</span><span>.</span><span>default</span><span>])
</span></span><span><span>)
</span></span><span><span>&lt;/</span><span>script</span><span>&gt;
</span></span><span><span>
</span></span><span><span>&lt;</span><span>template</span><span>&gt;
</span></span><span><span>  &lt;</span><span>div</span><span>&gt;
</span></span><span><span>    &lt;</span><span>label</span><span> v-for</span><span>=</span><span>"</span><span>doggo in dogNames</span><span>"</span><span> :key</span><span>=</span><span>"</span><span>doggo</span><span>"</span><span> style</span><span>=</span><span>"</span><span>margin-right: 2rem</span><span>"</span><span>&gt;
</span></span><span><span>      &lt;</span><span>input</span><span> type</span><span>=</span><span>"</span><span>radio</span><span>"</span><span> :value</span><span>=</span><span>"</span><span>doggo</span><span>"</span><span> v-model</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span> /&gt;
</span></span><span><span>      {{ doggo }}
</span></span><span><span>    &lt;/</span><span>label</span><span>&gt;
</span></span><span><span>    &lt;</span><span>img
</span></span><span><span>      :src</span><span>=</span><span>"</span><span>images[`${selectedDog.toLowerCase()}`]</span><span>"
</span></span><span><span>      width</span><span>=</span><span>"</span><span>500</span><span>"
</span></span><span><span>      :alt</span><span>=</span><span>"</span><span>selectedDog</span><span>"
</span></span><span><span>    /&gt;
</span></span><span><span>  &lt;/</span><span>div</span><span>&gt;
</span></span><span><span>&lt;/</span><span>template</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->To see the code in action, you can also check out the <a><!--[--><!--[-->StackBlitz<!--]--><!--]--></a>.<!--]--></p><p><!--[-->And we achieve a similar result to the </p><div><span></span><!--[-->public<!--]--><!----></div> folder approach, but with the benefits of the <div><span></span><!--[-->assets<!--]--><!----></div> folder:<!--]--><p></p><ul><!--[--><li><!--[-->Caching: As vite will add a hash to the name of each image, we can change the image without changing the file name and the cache will be busted.<!--]--></li><li><!--[-->Image optimization: The image can be optimized by vite plugins like <a><!--[--><!--[--><div><span></span><!--[-->vite-plugin-image-optimizer<!--]--><!----></div><!--]--><!--]--></a>, so we don't have to do it manually.<!--]--></li><!--]--></ul><p><!--[-->But it has also some downsides:<!--]--></p><ul><!--[--><li><!--[-->Image name: The image name will be changed, so linking it outside of your app will be nearly impossible<!--]--></li><li><!--[-->Perf-overhead: Importing the modules via glob add some overhead to the computed time, which is not optimal.<!--]--></li><!--]--></ul><h2><a><!--[-->The solution<!--]--></a></h2><p><!--[-->Both approaches have their pros and cons as you can see.
I'd recommend using the </p><div><span></span><!--[-->asset<!--]--><!----></div> folder approach only for static images which might change often in the future.<!--]--><p></p><p><!--[-->The </p><div><span></span><!--[-->public<!--]--><!----></div> folder approach is a good default solution, especially if you use the <a><!--[--><!--[-->Nuxt image module<!--]--><!--]--></a> to optimize your images. The module actually <em><!--[-->requires<!--]--></em> the images to be in the <div><span></span><!--[-->public<!--]--><!----></div> folder.
Also, this approach is the easiest solution to implement, as you don't have to do any extra work.
The only downside is that the image will be cached and you can't bust the cache as easy as with the vite-based approach.<!--]--><p></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->If you want to replicate a webpack-like behavior in Vite, loading images with dynamic paths is not as easy as it might seem at first glance.
But with the help of </p><div><span></span><!--[-->import.meta.glob<!--]--><!----></div> and some extra work, we can achieve a similar result if that's the requirement.
With the <div><span></span><!--[-->public<!--]--><!----></div> folder approach, we can load images dynamically without any extra work, but we have to keep in mind that the image will be cached and not optimized (so you better use the Nuxt image module).<!--]--><p></p><p><!--[-->Still have questions? No problem, drop me a Tweet (or however it is called now) at <a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io).<!--]--></p><p><!--[-->I hope you enjoyed this article and learned something new! If you did, please consider sharing it with your friends and colleagues. Thanks for reading!<!--]--></p><style>html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .sNpkn, html code.shiki .sNpkn{--shiki-default:#DBD7CAEE}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .s6USN, html code.shiki .s6USN{--shiki-default:#B8A96577}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Change the Nuxt 2 server error page]]></title>
            <link>https://www.lichter.io/articles/nuxt-change-server-error-page/</link>
            <guid>https://www.lichter.io/articles/nuxt-change-server-error-page/</guid>
            <pubDate>Tue, 18 Aug 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[Almost every Nuxt.js developer has seen the "evil grey page" that is displayed after a server-side error (for example a failing API call in asyncData or fetch without a try-catch block around it). Pretty sure many of you want to change it to make it fit to your design.]]></description>
            <content:encoded><![CDATA[<p><!--[-->I bet every developer using Nuxt 2 has seen this error page at least once:<!--]--></p><p><!--[--><img><!--]--></p><p><!--[-->And I’m sure that you’ve asked yourself: <em><!--[-->How to change this page?<!--]--></em> You likely want to brand this error page similar to your <em><!--[-->client side error pages<!--]--></em>, so that your users aren’t as confused whenever a server-side error occurs and have info available how to proceed further.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Testing the server-side error page<!--]--></a><!----></li><li><a><!--[-->Changing text<!--]--></a><!----></li><li><a><!--[-->Completely replace the error page<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Testing the server-side error page<!--]--></a></h2><p><!--[-->Let’s create a new dummy page called </p><div><span></span><!--[-->error.vue<!--]--><!----></div> in the <div><span></span><!--[-->pages<!--]--><!----></div> folder:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  asyncData</span><span>()</span><span> {
</span></span><span><span>    throw</span><span> new</span><span> Error</span><span>(</span><span>'</span><span>:(</span><span>'</span><span>)
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->To see the page you have to boot up the project in <strong><!--[-->production mode<!--]--></strong>. Otherwise </p><div><span></span><!--[-->youch<!--]--><!----></div> will kick in and give you information about the occurred error. After going through <div><span></span><!--[-->npm run build &amp;&amp; npm start<!--]--><!----></div>, open the page (f.ex. with <div><span></span><!--[-->localhost:3000/error<!--]--><!----></div>) and you should see the error page.<!--]--><p></p><p><!--[-->Okay… changing time!<!--]--></p><h2><a><!--[-->Changing text<!--]--></a></h2><p><!--[-->Do you like the style and only want to change the text there? No problem! You can do this through your </p><div><span></span><!--[-->nuxt.config.js<!--]--><!----></div>. The text for the default error pages is extracted from the <div><span></span><!--[-->message<!--]--><!----></div> object. The important default values can be found below:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  messages</span><span>:</span><span> {
</span></span><span><span>      loading</span><span>:</span><span> '</span><span>Loading...</span><span>'</span><span>,
</span></span><span><span>      error_404</span><span>:</span><span> '</span><span>This page could not be found</span><span>'</span><span>,
</span></span><span><span>      server_error</span><span>:</span><span> '</span><span>Server error</span><span>'</span><span>,
</span></span><span><span>      nuxtjs</span><span>:</span><span> '</span><span>Nuxt.js</span><span>'</span><span>,
</span></span><span><span>      back_to_home</span><span>:</span><span> '</span><span>Back to the home page</span><span>'</span><span>,
</span></span><span><span>      server_error_details</span><span>:
</span></span><span><span>        '</span><span>An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details.</span><span>'</span><span>,
</span></span><span><span>      client_error</span><span>:</span><span> '</span><span>Error</span><span>'</span><span>,
</span></span><span><span>      client_error_details</span><span>:
</span></span><span><span>        '</span><span>An error occurred while rendering the page. Check developer tools console for details.</span><span>'
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->I’ve highlighted the lines that influence the text of the default server error page. Let’s change them and see what happens!<!--]--></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  messages</span><span>:</span><span> {
</span></span><span><span>      server_error</span><span>:</span><span> '</span><span>Oh no! Server error</span><span>'</span><span>,
</span></span><span><span>      nuxtjs</span><span>:</span><span> '</span><span>Is this Nuxt.js?</span><span>'</span><span>,
</span></span><span><span>      back_to_home</span><span>:</span><span> '</span><span>Cmon, back home!</span><span>'</span><span>,
</span></span><span><span>      server_error_details</span><span>:</span><span> '</span><span>Uh uh :| Server errorrrrr</span><span>'</span><span>,
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[--><img><!--]--></p><p><!--[-->Looks great so far! But as you may think, this isn’t everything ?<!--]--></p><h2><a><!--[-->Completely replace the error page<!--]--></a></h2><p><!--[-->Surprise folks! Similar to an own </p><div><span></span><!--[-->app.html<!--]--><!----></div> file, you can also replace the server-side error page.<!--]--><p></p><p><!--[-->All you have to do is to create a folder called </p><div><span></span><!--[-->app<!--]--><!----></div> in your project root and another one inside it called <div><span></span><!--[-->views<!--]--><!----></div>. Now add an <div><span></span><!--[-->error.html<!--]--><!----></div> file inside the <div><span></span><!--[-->views<!--]--><!----></div> folder and edit it to customize your error page.<!--]--><p></p><p><!--[--></p><div><span></span><!--[-->HTML<!--]--><!----></div> means: No Vue/Nuxt logic, only pure HTML and CSS. You can add javascript (eg. error reporting tools) and so on if you want though.<!--]--><p></p><p><!--[-->I’ll add a small unstyled piece of HTML for demo purposes.<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>h1</span><span>&gt;</span><span>Oh No :(</span><span>&lt;/</span><span>h1</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[--><img><!--]--></p><p><!--[-->There we go!<!--]--></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->It was easier than you thought, wasn’t it? Now it’s your turn! ? I’d love to see some nice error page designs, so if you built a great one and want to share it be sure to send it to me as well.<!--]--></p><p><!--[-->As usual, I hope the article helped you out somehow. If you’ve spotted typos, wrong code or have questions, feedback or ideas, <strong><!--[-->please send me a message<!--]--></strong>!<!--]--></p><p><!--[-->You can reach out on Twitter (<a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>) or through email (blog at lichter dot io). Stay tuned for more content ?<!--]--></p><style>html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sNpkn, html code.shiki .sNpkn{--shiki-default:#DBD7CAEE}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What to do when Vue 2 hydration fails]]></title>
            <link>https://www.lichter.io/articles/vue-hydration-error/</link>
            <guid>https://www.lichter.io/articles/vue-hydration-error/</guid>
            <pubDate>Tue, 14 Apr 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[SSR is amazing but also comes with errors you might have not seen before. Especially one problem still boggles lots of minds: When Vue Hydration fails. In this article we will take a look at possible reasons, explain what the error means and how to fix it.]]></description>
            <content:encoded><![CDATA[<p><!--[-->Server-side rendering in Vue has lots of benefits. Especially with frameworks like Nuxt.js or Gridsome, developing SSR Vue applications is a breeze, no matter if you use dynamic SSR or static site generation. But on the other hand, server-side rendering also comes with a fair increase of complexity and errors you might have not seen before. While most of them are documented and workarounds are available, one error still boggles lots of minds: When <strong><!--[-->Vue hydration fails<!--]--></strong>.<!--]--></p><p><!--[-->In this article we will take a look at possible reasons, explain what the error means and furthermore go through solutions and debugging opportunities:<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->What is Vue Hydration?<!--]--></a><!----></li><li><a><!--[-->How to recognize failing hydration<!--]--></a><!----></li><li><a><!--[-->Common Causes<!--]--></a><ul><!--[--><li><a><!--[-->Invalid HTML<!--]--></a><!----></li><li><a><!--[-->Scripts altering the HTML<!--]--></a><!----></li><li><a><!--[-->Different state on the server and client<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Solving the hydration failure<!--]--></a><ul><!--[--><li><a><!--[-->Find the element causing the hydration error<!--]--></a><!----></li><li><a><!--[-->Ensure your HTML is valid<!--]--></a><!----></li><li><a><!--[-->Resolving state inconsistencies between server and client<!--]--></a><!----></li><li><a><!--[-->Final escape hatch: &lt;ClientOnly&gt;<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->What is Vue Hydration?<!--]--></a></h2><p><!--[-->When I heard the term <em><!--[-->Hydration<!--]--></em> for the first time, it felt very abstract to me and I couldn’t think of the meaning. Eventually, I realized it’s not as complex as the term sounds at first:<!--]--></p><blockquote><!--[--><p><!--[--><em><!--[-->Hydration is the process where Vue is transforming server-side rendered markup and makes it reactive so it can reflect dynamic changes from Vue.<!--]--></em><!--]--></p><!--]--></blockquote><p><!--[-->If Vue is expecting a different markup compared to the rendered HTML, the <strong><!--[-->hydration will fail<!--]--></strong> (also called “Vue will bail hydration”). You can read more in the <a><!--[--><!--[-->official Vue SSR docs<!--]--><!--]--></a> about it.<!--]--></p><h2><a><!--[-->How to recognize failing hydration<!--]--></a></h2><p><!--[-->We now know what hydration is and when it fails. But how can we developers discover that hydration did not work as expected? Well, there are two error messages which definitely point to failing hydration but both come with constraints.<!--]--></p><p><!--[-->The first one appears <strong><!--[-->only in development<!--]--></strong> regardless of the mode:<!--]--></p><pre><!--[--><code><span><span>Parent:  &lt;div class="container"&gt; client-hook-3.js:1:16358
</span></span><span><span>Mismatching childNodes vs. VNodes: NodeList(3) [ p, p, p ]  Array [ {…} ]
</span></span><span><span>    
</span></span><span><span>[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content.
</span></span><span><span>This is likely caused by incorrect HTML markup, for example nesting block-level elements inside &lt;p&gt;, or missing &lt;tbody&gt;. 
</span></span><span><span>Bailing hydration and performing full client-side render.
</span></span></code><!--]--></pre><p><!--[-->The second error message shows up <strong><!--[-->only in production<!--]--></strong> and when using <strong><!--[-->static site generation<!--]--></strong>:<!--]--></p><pre><!--[--><code><span><span>Error: [nuxt] Error while mounting app: HierarchyRequestError: Failed to execute 'appendChild' on 'Node':
</span></span><span><span>This node type does not support this method. at some-file.js:1
</span></span></code><!--]--></pre><p><!--[-->As we know, hydration only happens when the page is rendered by the server in the first place, so usually only on the initial request to your application.<!--]--></p><p><!--[-->This makes it even more difficult to spot hydration problems because they are not visible when navigating between pages through a </p><div><span></span><!--[-->&lt;AppLink&gt;<!--]--><!----></div> but only on a hard reload.<!--]--><p></p><p><!--[-->Thus, hydration errors are sometimes only discovered on the staging system or worse - only in production. And in rare case, there isn’t even a console error logged but some components simply stop working.<!--]--></p><h2><a><!--[-->Common Causes<!--]--></a></h2><p><!--[-->Now that we know how to spot failing hydration we will look into typical causes for Vue to bail hydration. We can’t cover every possible reason because they vary a lot and depend a lot on your code.<!--]--></p><p><!--[-->For the upcoming chapters, every time the <em><!--[-->server<!--]--></em> or <em><!--[-->server-side<!--]--></em> is mentioned, it is relevant for both scenarios - dynamic SSR and static site generation - as both have technically a server rendering content (unless stated otherwise).<!--]--></p><h3><a><!--[-->Invalid HTML<!--]--></a></h3><p><!--[-->Invalid HTML is the first thing you should check for when the hydration error pops up. This is also what one of the error messages suggest.<!--]--></p><blockquote><!--[--><p><!--[--></p><div><span></span><!--[-->This is likely caused by incorrect HTML markup, for example nesting block-level elements inside &lt;p&gt;, or missing &lt;tbody&gt;<!--]--><!----></div><!--]--><p></p><!--]--></blockquote><p><!--[-->Unfortunately, invalid HTML is often <strong><!--[-->not the reason<!--]--></strong> for the failing hydration. Nevertheless, you should double-check your markup. Also make sure you check your minification settings, as aggressive HTML minification could lead to invalid HTML.If you have user-generated output or content coming from a CMS, it’s worth verifying that this content is valid HTML too. Finally, also third-party plugins or services could influence and manipulate the HTML. A common example for the latter is Cloudflare, when you’ve enabled their services like HTML minification, the “Rocket loader” or other features altering the page content.<!--]--></p><p><!--[-->I’ve created a simple <a><!--[--><!--[-->example codesandbox<!--]--><!--]--></a> containing invalid HTML and triggering a hydration failure.<!--]--></p><h3><a><!--[-->Scripts altering the HTML<!--]--></a></h3><p><!--[-->Talking about scripts: If you have added third party javascript files to your Vue application, these can also alter the HTML (e.g. by embedding a form) before Vue can take over and hydrate the HTML coming from the server.<!--]--></p><h3><a><!--[-->Different state on the server and client<!--]--></a></h3><p><!--[-->Having a different state on the server and client is the most common reason for hydration. As usual, reasons for the inconsistencies can vary a lot.<!--]--></p><h4><a><!--[-->Dates, Timestamps, and Randomizing<!--]--></a></h4><p><!--[-->When you include dates or timestamps on your website you should be careful and make them as <em><!--[-->static<!--]--></em> as possible, especially if your site is generated statically. If the client evaluates an expression like </p><div><span></span><!--[-->new Date()<!--]--><!----></div> it will likely be different than the date generated on your server when it retrieved the same date during deployment. This also <a><!--[--><!--[-->bit me on my company’s about page<!--]--><!--]--></a> where I wanted to shuffle the order of the people displayed based on the current minute.<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> const</span><span> deterministicRotate</span><span> =</span><span> (</span><span>arr</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>arr</span><span>.</span><span>length</span><span> &lt;=</span><span> 1</span><span>)</span><span> {
</span></span><span><span>    return</span><span> arr
</span></span><span><span>  }
</span></span><span><span>  const</span><span> rotations</span><span> =</span><span> (</span><span>new</span><span> Date</span><span>()).</span><span>getMinutes</span><span>()</span><span> %</span><span> arr</span><span>.</span><span>length
</span></span><span><span>
</span></span><span><span>  return</span><span> rotations</span><span> ?</span><span> arr</span><span> :</span><span> arr</span><span>.</span><span>reverse</span><span>()
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->The plan was to reverse the array if the minute where the user opened the page is odd. That worked very well when using dynamic SSR. But when switching to a JAMstack site which is statically generated, the feature turned into a bug. You can try it out by clicking on the link above and refresh after a minute. What happens is that names and text of the people are swapped correctly but the images stay the same. <strong><!--[-->Horrible!<!--]--></strong> And it happens because of the date mismatch between the server and the client. After <a><!--[--><!--[-->removing the deterministic shuffle code<!--]--><!--]--></a> everything worked again as usual.<!--]--></p><h4><a><!--[-->Authentication<!--]--></a></h4><p><!--[-->Another common reason for inconsistencies is user authentication. This applies to both, dynamic SSR and static site generation.<!--]--></p><p><!--[-->When storing the authentication state only on the client side (e.g. in the localStorage), the server “does not know about the authentication”. This will inevitably lead to hydration issues because the server and client information is fundamentally different when you are logged in. Thus, you should not render any authentication-related component on the server-side if the server is not aware of the authentication state <strong><!--[-->or your are generating your page statically<!--]--></strong>.<!--]--></p><p><!--[-->You may wonder why it always applies to static sites: Because when you generate your site, it’s HTML and serialized code is “stateless”. We can’t take the “logged in user state” into account during the build phase. This means you have to exclude all authentication-related components from rendering on the server.<!--]--></p><h4><a><!--[-->And there is still more<!--]--></a></h4><p><!--[-->Besides these two scenarios, there are even more edge cases that could hit you and cause inconsistencies. Even if it’s not listed here, we will solve the hydration error! At first, we will narrow it down to the DOM element causing the problem.<!--]--></p><h2><a><!--[-->Solving the hydration failure<!--]--></a></h2><h3><a><!--[-->Find the element causing the hydration error<!--]--></a></h3><p><!--[-->To narrow down the problem to a specific component or DOM element we can use the devtools of your favorite browser!<!--]--></p><h4><a><!--[-->Setting up the debuggers<!--]--></a></h4><ol><!--[--><li><!--[-->Ensure you are in dev mode<!--]--></li><li><!--[-->Open up the DevTools (usually by pushing F12)<!--]--></li><li><!--[-->Trigger a hydration warning (usually by reloading the site)<!--]--></li><li><!--[-->Unfold the <div><span></span><!--[-->[Vue Warn] The client side ...<!--]--><!----></div> error message to see the stack trace (depending on the browser, also unfold the “VueJS” list popping up)<!--]--></li><li><!--[-->Click on one of the <div><span></span><!--[-->hydrate<!--]--><!----></div> calls. This will open up the source code of Vue’s hydration function.<!--]--></li><li><!--[-->Now, set a debugger whenever the function returns <div><span></span><!--[-->false<!--]--><!----></div>. By the time of writing, this happens three times:<!--]--></li><!--]--></ol><pre><!--[--><code><span><span>if</span><span> (</span><span>process</span><span>.</span><span>env</span><span>.</span><span>NODE_ENV</span><span> !==</span><span> '</span><span>production</span><span>'</span><span>)</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>assertNodeMatch</span><span>(</span><span>elm</span><span>,</span><span> vnode</span><span>,</span><span> inVPre</span><span>))</span><span> {
</span></span><span><span>      return</span><span> false</span><span> //HERE
</span></span><span><span>  }
</span></span><span><span>}
</span></span><span><span>
</span></span><span><span>
</span></span><span><span>    if</span><span> (</span><span>process</span><span>.</span><span>env</span><span>.</span><span>NODE_ENV</span><span> !==</span><span> '</span><span>production</span><span>'</span><span> &amp;&amp;
</span></span><span><span>          typeof</span><span> console</span><span> !==</span><span> '</span><span>undefined</span><span>'</span><span> &amp;&amp;
</span></span><span><span>          !</span><span>hydrationBailed
</span></span><span><span>    )</span><span> {
</span></span><span><span>        hydrationBailed</span><span> =</span><span> true</span><span>;
</span></span><span><span>        console</span><span>.</span><span>warn</span><span>(</span><span>'</span><span>Parent: </span><span>'</span><span>,</span><span> elm</span><span>);
</span></span><span><span>        console</span><span>.</span><span>warn</span><span>(</span><span>'</span><span>server innerHTML: </span><span>'</span><span>,</span><span> i</span><span>);
</span></span><span><span>        console</span><span>.</span><span>warn</span><span>(</span><span>'</span><span>client innerHTML: </span><span>'</span><span>,</span><span> elm</span><span>.</span><span>innerHTML</span><span>);
</span></span><span><span>    }
</span></span><span><span>  return</span><span> false</span><span> //HERE
</span></span><span><span>}
</span></span><span><span>
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>process</span><span>.</span><span>env</span><span>.</span><span>NODE_ENV</span><span> !==</span><span> '</span><span>production</span><span>'</span><span> &amp;&amp;
</span></span><span><span>      typeof</span><span> console</span><span> !==</span><span> '</span><span>undefined</span><span>'</span><span> &amp;&amp;
</span></span><span><span>      !</span><span>hydrationBailed
</span></span><span><span>  )</span><span> {
</span></span><span><span>    hydrationBailed</span><span> =</span><span> true</span><span>;
</span></span><span><span>    console</span><span>.</span><span>warn</span><span>(</span><span>'</span><span>Parent: </span><span>'</span><span>,</span><span> elm</span><span>);
</span></span><span><span>    console</span><span>.</span><span>warn</span><span>(</span><span>'</span><span>Mismatching childNodes vs. VNodes: </span><span>'</span><span>,</span><span> elm</span><span>.</span><span>childNodes</span><span>,</span><span> children</span><span>);
</span></span><span><span>  }
</span></span><span><span>  return</span><span> false</span><span> //HERE
</span></span><span><span>}
</span></span></code><!--]--></pre><h4><a><!--[-->Time to debug<!--]--></a></h4><ol><!--[--><li><!--[-->Last but not least, let the hydration error appear again. Often, this is possible by reloading the page again but sometimes it’s more difficult.<!--]--></li><li><!--[-->You now see that one of our breakpoints was triggered and script execution is stopped
. Now open the DevTool’s console and write <div><span></span><!--[-->elm<!--]--><!----></div> to get the DOM element where hydration fails. With the DOM element, you 1should be able to trace back the hydration error to one of your Vue components<!--]--></li><li><!--[-->Continue with the next steps<!--]--></li><!--]--></ol><p><!--[-->PS: This is an adapted workflow of <a><!--[--><!--[-->this StackOverflow answer<!--]--><!--]--></a> by user budden73.<!--]--></p><h3><a><!--[-->Ensure your HTML is valid<!--]--></a></h3><p><!--[-->Now that you found the code causing the problem, the first thing you should do is to verify that your markup (possibly coming from an API) is valid. Code like </p><div><span></span><!--[-->&lt;p&gt;&lt;p&gt;Text&lt;/p&gt;&lt;/p&gt;<!--]--><!----></div> is not valid because a <div><span></span><!--[-->p<!--]--><!----></div> element doesn’t allow other block elements (like a paragraph tag) inside.<!--]--><p></p><p><!--[-->Be aware, that </p><div><span></span><!--[-->&lt;span&gt;<!--]--><!----></div> tags are not allowed to have block level elements like <div><span></span><!--[-->&lt;div&gt;<!--]--><!----></div> or <div><span></span><!--[-->&lt;p&gt;<!--]--><!----></div> as children. These <div><span></span><!--[-->&lt;span&gt;<!--]--><!----></div> tags are used default tag for Vue’s transitions though. You can change that though via <div><span></span><!--[-->&lt;Transition tag="div"&gt;<!--]--><!----></div>.<!--]--><p></p><h3><a><!--[-->Resolving state inconsistencies between server and client<!--]--></a></h3><p><!--[-->During the debugging, you were able to take a look at the results from the server and the (re-rendered) client-side part. If these are different, you can take a look at how you fetch data and what you render on the server/client-side. One common issue is authentication for static pages. Because the HTML generated at build time is <em><!--[-->stateless<!--]--></em>, thus not knowing about any authentication state, all parts of your application that are related to authentication should only be rendered on the client-side. Otherwise, the client, which has the authentication status of the user, expects different HTML from the server because the user is logged in. Then there is only one option left…<!--]--></p><h3><a><!--[-->Final escape hatch: <div><span></span><!--[-->&lt;ClientOnly&gt;<!--]--><!----></div><!--]--></a></h3><p><!--[-->The last option to resolve hydration errors is to avoid them at all for the component. This is mandatory for authentication-related components on statically generated pages and sometimes also for components delivering content you can’t change but must embed, e.g. from 3rd party applications.<!--]--></p><p><!--[-->As we have learned at the beginning of the post, hydration only happens when the component is rendered on both, client and server side. To avoid hydration, we avoid rendering the component on the server-side by wrapping it in a </p><div><span></span><!--[-->&lt;ClientOnly&gt;<!--]--><!----></div> tag.<!--]--><p></p><p><!--[-->The only drawback: The component is not included in the HTML returned by the server and not helpful for SEO.<!--]--></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->Let’s wrap it up! Now you know more about:<!--]--></p><ul><!--[--><li><!--[-->What hydration is and what it does<!--]--></li><li><!--[-->How hydration can fail and how to spot hydration errors<!--]--></li><li><!--[-->Common reasons for bailed hydration<!--]--></li><li><!--[-->How to debug <em><!--[-->your<!--]--></em> hydration error and fix your application<!--]--></li><!--]--></ul><p><!--[-->I hope that this post was insightful and you’ve learned a thing or two. Are you experiencing causes for hydration errors I haven’t described here or did I miss a common reason? Feel free to <strong><!--[-->message me<!--]--></strong> on Twitter or by mail.<!--]--></p><p><!--[-->And as usual - I’d be glad if you could spread the word and share the blog post with colleagues ?<!--]--></p><p><!--[-->See you around!<!--]--></p><style>html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sxA9i, html code.shiki .sxA9i{--shiki-default:#4C9A91}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sNpkn, html code.shiki .sNpkn{--shiki-default:#DBD7CAEE}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to load dynamic images in Vue 2 and Nuxt 2 with ease]]></title>
            <link>https://www.lichter.io/articles/nuxt2-vue2-dynamic-images/</link>
            <guid>https://www.lichter.io/articles/nuxt2-vue2-dynamic-images/</guid>
            <pubDate>Sun, 18 Aug 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Loading images with a dynamic source often confuses developers that are new to Vue and Nuxt.js. In the following article, I want to demystify the process of dynamic image loading in Vue and Nuxt, and explain why static images can be loaded easily...]]></description>
            <content:encoded><![CDATA[<blockquote><!--[--><p><!--[-->This article is written for <strong><!--[-->Vue 2<!--]--></strong> and <strong><!--[-->Nuxt 2<!--]--></strong>.<!--]--></p><p><!--[-->If you are looking for the <strong><!--[-->Nuxt 3<!--]--></strong>/<strong><!--[-->Vue 3<!--]--></strong> version of this article, please <a><!--[--><!--[-->follow this link to the updated Nuxt 3 / Vue 3 version<!--]--><!--]--></a>.<!--]--></p><!--]--></blockquote><p><!--[-->Importing images from the </p><div><span></span><!--[-->assets<!--]--><!----></div> folder when the path is static is not that difficult in both frameworks, Vue and Nuxt.js. But loading images with a dynamic source often confuses developers that are fairly new to one of these frameworks. In the following article I want to demystify the process of dynamic image loading in Vue and Nuxt. Furthermore I’ll explain why static images can be loaded easily and what to do when the path has to be dynamic. If you want to skip the internals and explanations, you can also go directly to <a><!--[--><!--[-->the solution<!--]--><!--]--></a> and the CodeSandbox. But you are missing out some in-depth information!<!--]--><p></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Static images<!--]--></a><!----></li><li><a><!--[-->The typical first idea<!--]--></a><!----></li><li><a><!--[-->The public/static folder as a possible workaround (usually not recommended)<!--]--></a><!----></li><li><a><!--[-->Vue and Webpack asset handling<!--]--></a><!----></li><li><a><!--[-->The solution<!--]--></a><!----></li><li><a><!--[-->Bonus - using srcset<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Static images<!--]--></a></h2><p><!--[-->Imagine a component called <em><!--[-->Doggos<!--]--></em> that should show an image of a cute puppy. I mean, we all love puppies, don’t we?<!--]--></p><p><!--[-->The only thing our components needs is a template with a single image tag pointing to the path. Ideally by utilizing an alias:<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> src</span><span>=</span><span>"</span><span>@/assets/doggos/riley.jpg</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->(with an alias for the source directory)<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> src</span><span>=</span><span>"</span><span>../assets/doggos/riley.jpg</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->(relative path without alias)<!--]--></p><p><!--[-->But what if we have a list of cute puppies and the user can decide which image to display on the page?<!--]--></p><h2><a><!--[-->The typical first idea<!--]--></a></h2><p><!--[-->A common attempt to load a dynamic image source in Vue or Nuxt is to utilize Vue’s binding system. Imagine we have a small component set up where we can select a puppy:<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>template</span><span>&gt;
</span></span><span><span>  &lt;</span><span>div</span><span>&gt;
</span></span><span><span>    &lt;</span><span>div</span><span>&gt;
</span></span><span><span>      &lt;</span><span>label</span><span> v-for</span><span>=</span><span>"</span><span>doggo in dogNames</span><span>"</span><span> :key</span><span>=</span><span>"</span><span>doggo</span><span>"</span><span> style</span><span>=</span><span>"</span><span>margin-right: 2rem</span><span>"</span><span>&gt;
</span></span><span><span>        &lt;</span><span>input</span><span> type</span><span>=</span><span>"</span><span>radio</span><span>"</span><span> :value</span><span>=</span><span>"</span><span>doggo</span><span>"</span><span> v-model</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span>&gt;
</span></span><span><span>        {{ doggo }}
</span></span><span><span>      &lt;/</span><span>label</span><span>&gt;
</span></span><span><span>    &lt;/</span><span>div</span><span>&gt;
</span></span><span><span>    &lt;!-- Here should be the dog image --&gt;
</span></span><span><span>  &lt;/</span><span>div</span><span>&gt;
</span></span><span><span>&lt;/</span><span>template</span><span>&gt;
</span></span><span><span>
</span></span><span><span>&lt;</span><span>script</span><span>&gt;
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> {
</span></span><span><span>  data</span><span> ()</span><span> {
</span></span><span><span>    return</span><span> {
</span></span><span><span>      selectedDog</span><span>:</span><span> ""</span><span>,
</span></span><span><span>      dogNames</span><span>:</span><span> [</span><span>"</span><span>Riley</span><span>"</span><span>,</span><span> "</span><span>Annie</span><span>"</span><span>,</span><span> "</span><span>Marvin</span><span>"</span><span>]
</span></span><span><span>    }
</span></span><span><span>  }
</span></span><span><span>}
</span></span><span><span>&lt;/</span><span>script</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->All that is left to do is to retrieve the correct image for the </p><div><span></span><!--[-->selectedDog<!--]--><!----></div>. Now one could think: “Nothing easier than that! <div><span></span><!--[-->:src<!--]--><!----></div> to the rescue!”<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> :</span><span>src</span><span>=</span><span>"</span><span>`</span><span>../assets/doggos/</span><span>${</span><span>selectedDog</span><span>.</span><span>toLowerCase</span><span>()</span><span>}</span><span>.jpg</span><span>`</span><span>"</span><span> :</span><span>alt</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->Let’s add that line quickly and see what happens when we push the button mapped to Riley…<!--]--></p><p><!--[--><strong><!--[-->Bummer, a broken image<!--]--></strong> and only the alt tag! Let us take a look at the DOM. It contains the following image tag:<!--]--></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> src</span><span>=</span><span>"</span><span>../assets/doggos/riley.jpg</span><span>"</span><span> alt</span><span>=</span><span>"</span><span>Riley</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->What does that mean?<!--]--></p><p><!--[-->It means that the <strong><!--[-->asset path hasn’t been replaced<!--]--></strong>. It is the string that the expression in our template string above evaluates to.<!--]--></p><h2><a><!--[-->The <div><span></span><!--[-->public<!--]--><!----></div>/<div><span></span><!--[-->static<!--]--><!----></div> folder as a possible workaround (usually not recommended)<!--]--></a></h2><p><!--[-->If we move our dog images into the </p><div><span></span><!--[-->public<!--]--><!----></div> folder (or the <div><span></span><!--[-->static<!--]--><!----></div> folder in Nuxt), and use the code from above with the new folder reference (<div><span></span><!--[-->/public/doggo/${selectedDog.toLowercase()}.jpg<!--]--><!----></div>), “<em><!--[-->it works<!--]--></em>”. But it is not optimal and I really <strong><!--[-->would not recommend that workaround<!--]--></strong>. The difference between the two folders <div><span></span><!--[-->assets<!--]--><!----></div> and <div><span></span><!--[-->public<!--]--><!----></div>/<div><span></span><!--[-->static<!--]--><!----></div>, and the reason why the first attempt failed, is <strong><!--[-->Webpack<!--]--></strong>.<!--]--><p></p><p><!--[-->Content in the </p><div><span></span><!--[-->public<!--]--><!----></div> or <div><span></span><!--[-->static<!--]--><!----></div> folder, is directly mapped to the root of your web applications (usually <div><span></span><!--[-->/<!--]--><!----></div>) and <em><!--[-->not processed by Webpack<!--]--></em>. No optimizations, added content hashes and so on.<!--]--><p></p><p><!--[-->While it is sometimes necessary, for example for preview images which need a fixed URL, it brings no benefits for our use case. What if we want to swap out the image for Marvin when he grew up a little? We might hit caching issues. Let’s dig a bit deeper and find the root cause instead of going for the “quick fix” which will consume more time on the long run. You likely know that pattern<!--]--></p><h2><a><!--[-->Vue and Webpack asset handling<!--]--></a></h2><p><!--[-->All single file components (with the </p><div><span></span><!--[-->.vue<!--]--><!----></div> extension) are processed by Webpack and the <a><!--[--><!--[--><div><span></span><!--[-->vue-loader<!--]--><!----></div><!--]--><!--]--></a>. Because of them, single file components and all their amazing features, like supporting CSS pre-processors, custom blocks and state-preserving hot reloading, work out of the box. This also includes the <a><!--[--><!--[-->handling of <strong><!--[-->static assets<!--]--></strong><!--]--><!--]--></a>, like in our initial component example.<!--]--><p></p><p><!--[-->Webpack will import static assets like </p><div><span></span><!--[-->../assets/doggos/riley.jpg<!--]--><!----></div> as so-called <em><!--[-->module requests<!--]--></em>, which means they are handled by a matching Webpack loader defined through a Webpack config. Both, <div><span></span><!--[-->vue-cli<!--]--><!----></div> and Nuxt have configured the handling for multiple file types out of the box, including images with different extensions like jpg, png or gif.<!--]--><p></p><p><!--[-->After being processed, our initial image tag example </p><div><span></span><!--[-->&lt;img src="../assets/doggos/riley.jpg" alt="Riley"&gt;<!--]--><!----></div> will be compiled to a render function that will look similar to this code:<!--]--><p></p><pre><!--[--><code><span><span>createElement</span><span>(</span><span>'</span><span>img</span><span>'</span><span>,</span><span> {
</span></span><span><span>  attrs</span><span>:</span><span> {
</span></span><span><span>    src</span><span>:</span><span> require</span><span>(</span><span>'</span><span>../assets/doggos/riley.jpg</span><span>'</span><span>),</span><span> // this is now a module request
</span></span><span><span>    alt</span><span>:</span><span> '</span><span>Riley</span><span>'
</span></span><span><span>    }
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->The image path has now been replaced with a Webpack <em><!--[-->module request<!--]--></em>. This works fine for static assets because <strong><!--[-->their paths are known at build time.<!--]--></strong> When it comes to dynamic content, including Vue’s </p><div><span></span><!--[-->v-bind<!--]--><!----></div> directive, Webpack does not know what the expressions at runtime will evaluate to. That’s the reason why the common first idea as described above does not work as expected.<!--]--><p></p><p><!--[-->What can we do then?<!--]--></p><p><!--[-->If we take a closer look at the compiled code of our simple image tag with the static source attribute, we can find out what we need to solve the issue.<!--]--></p><h2><a><!--[-->The solution<!--]--></a></h2><p><!--[-->To tell Webpack which images should be loaded, we have to issue a <em><!--[-->module request<!--]--></em> on our own. This is done by calling </p><div><span></span><!--[-->require(...)<!--]--><!----></div> with the correct path. In our situation, that would lead to the following image tag:<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>img</span><span> :</span><span>src</span><span>=</span><span>"</span><span>require</span><span>(</span><span>`</span><span>../assets/doggos/</span><span>${</span><span>selectedDog</span><span>.</span><span>toLowerCase</span><span>()</span><span>}</span><span>.jpg</span><span>`</span><span>)</span><span>"</span><span> :</span><span>alt</span><span>=</span><span>"</span><span>selectedDog</span><span>"</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->Instead of binding the </p><div><span></span><!--[-->src<!--]--><!----></div> attribute to the image path, we bind it to the webpack module requested for the image path.<!--]--><p></p><p><!--[-->We can now extract that nasty part into an own computed property for better readability.<!--]--></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  // ...
</span></span><span><span>  computed</span><span>:</span><span> {
</span></span><span><span>    dogImage</span><span> ()</span><span> {
</span></span><span><span>      if</span><span> (</span><span>!</span><span>this</span><span>.</span><span>selectedDog</span><span>)</span><span> {
</span></span><span><span>        return
</span></span><span><span>      }
</span></span><span><span>
</span></span><span><span>      const</span><span> fileName</span><span> =</span><span> this</span><span>.</span><span>selectedDog</span><span>.</span><span>toLowerCase</span><span>()
</span></span><span><span>
</span></span><span><span>      return</span><span> require</span><span>(</span><span>`</span><span>../assets/doggos/</span><span>${</span><span>fileName</span><span>}</span><span>.jpg</span><span>`</span><span>)</span><span> // the module request
</span></span><span><span>    }
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->It is <strong><!--[-->very important<!--]--></strong> that you are as strict as possible when it comes to the possible image file name. If we use the code from above, the final build will include every image with a </p><div><span></span><!--[-->.jpg<!--]--><!----></div> extension in the <div><span></span><!--[-->/assets/doggos<!--]--><!----></div> folder.<!--]--><p></p><p><!--[-->That happens because webpack cannot guess which of the images will actually be used at runtime, so it includes them all to prevent errors. The less strict you are, the more files will match and will be included in the bundle by webpack, leading to a larger bundle size.<!--]--></p><p><!--[--><strong><!--[-->We solved it<!--]--></strong>! With </p><div><span></span><!--[-->require<!--]--><!----></div> to the rescue, we can now load image with a dynamic src attribute as well. As usual, the final (working) code, including cute puppy pictures, is available <a><!--[--><!--[-->in a CodeSandbox to try it out<!--]--><!--]--></a>.<!--]--><p></p><h2><a><!--[-->Bonus - using srcset<!--]--></a></h2><p><!--[-->Nowadays, responsive images are a must-have on your website. They don’t only save bandwidth but also time of your users <em><!--[-->and<!--]--></em> are good for SEO. But how do we use </p><div><span></span><!--[-->srcset<!--]--><!----></div> with dynamic images?<!--]--><p></p><p><!--[--><em><!--[-->Almost<!--]--></em> the same way we do with the normal </p><div><span></span><!--[-->src<!--]--><!----></div> tag! As we need a couple of images now, a computed property is the best way to build your final srcset string step by step. In our example, we want to use two dog pictures with different widths. The name format is always &lt;DOG_NAME&gt;_.jpg, which we can utilize. And we will also re-use our <div><span></span><!--[-->dogImage<!--]--><!----></div> from above as fallback image.<!--]--><p></p><pre><!--[--><code><span><span>&lt;</span><span>template</span><span>&gt;
</span></span><span><span>  &lt;</span><span>img</span><span> :srcset</span><span>=</span><span>"</span><span>this.dogSourceset</span><span>"</span><span> sizes</span><span>=</span><span>"</span><span>(max-width: 600px) 480px, 800px</span><span>"</span><span> :src</span><span>=</span><span>"</span><span>dogImage</span><span>"</span><span>&gt;
</span></span><span><span>&lt;/</span><span>template</span><span>&gt;
</span></span><span><span>
</span></span><span><span>&lt;</span><span>script</span><span>&gt;
</span></span><span><span>export</span><span> default</span><span> {
</span></span><span><span>  // ...
</span></span><span><span>  computed</span><span>:</span><span> {
</span></span><span><span>    dogSourceset</span><span> ()</span><span> {
</span></span><span><span>        const</span><span> baseName</span><span> =</span><span> this</span><span>.</span><span>selectedDog</span><span>.</span><span>toLowerCase</span><span>()
</span></span><span><span>        return</span><span> `</span><span>${</span><span>require</span><span>(</span><span>`</span><span>@/assets/img/</span><span>${</span><span>this</span><span>.</span><span>baseName</span><span>}</span><span>_480.jpg</span><span>`</span><span>)</span><span>}</span><span> 480w, </span><span>${</span><span>require</span><span>(</span><span>`</span><span>@/assets/img/</span><span>${</span><span>this</span><span>.</span><span>baseName</span><span>}</span><span>_800.jpg</span><span>`</span><span>)</span><span>}</span><span> 800w</span><span>`
</span></span><span><span>    },
</span></span><span><span>    dogImage</span><span> ()</span><span> {</span><span> /* ... */</span><span> }
</span></span><span><span>  }
</span></span><span><span>}
</span></span><span><span>&lt;/</span><span>script</span><span>&gt;
</span></span></code><!--]--></pre><p><!--[-->There we go! A working image with srcset definition. And that’s not the end: You could even go further and create </p><div><span></span><!--[-->&lt;picture&gt;<!--]--><!----></div> tags with different sources that have their own srcsets (but e.g. a different format like webp and jpg). But it all comes down to the same technique I presented your here.<!--]--><p></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->Loading images with dynamic paths isn’t that difficult if one knows what’s going on behind the scenes. By using </p><div><span></span><!--[-->require<!--]--><!----></div> and a strict expression, you will never have problems with dynamic images again! I hope you’ve learned a thing or two, about dynamic images but also about the process behind static asset processing.<!--]--><p></p><p><!--[-->If you want some further reading, I suggest checking out the <a><!--[--><!--[-->vue-loader docs<!--]--><!--]--></a> and the <a><!--[--><!--[-->Webpack documentation<!--]--><!--]--></a>.<!--]--></p><p><!--[-->Still have questions? No problem, tweet me at <a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io).<!--]--></p><p><!--[-->I really hope you’ve enjoyed that article! If you know some people who also have trouble with dynamic image loading in Vue or Nuxt, I’d gladly ask you to <strong><!--[-->spread the word<!--]--></strong> and help them out!<!--]--></p><style>html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sNpkn, html code.shiki .sNpkn{--shiki-default:#DBD7CAEE}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sXjYR, html code.shiki .sXjYR{--shiki-default:#C99076}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Going Jamstack with Netlify and Nuxt 2]]></title>
            <link>https://www.lichter.io/articles/nuxt-jamstack-netlify/</link>
            <guid>https://www.lichter.io/articles/nuxt-jamstack-netlify/</guid>
            <pubDate>Tue, 29 Jan 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Jamstack is a growing and modern web architecture. I gradually migrated several Nuxt.js projects from server side rendering over to JAMstack and write about my experiences, recommendations and the migration process itself.]]></description>
            <content:encoded><![CDATA[<p><!--[-->In the last month, I gradually migrated several projects from server-rendered Nuxt.js applications to statically generated websites, powered by Nuxt.js too, as already hinted in my <a><!--[--><!--[-->previous article<!--]--><!--]--></a>.<!--]--></p><p><!--[-->It all started with my <a><!--[--><!--[-->portfolio<!--]--><!--]--></a> which consists of one larger page and two pages for privacy and legal notice. All three pages have actually no dynamic data involved (as you can see in the source code). The text, the images and everything else are completely hardcoded. Last year, the page was served from one of my Virtual Private Servers (VPS) by always running Node.js server. The only purpose of it was to respond with the same content again and again.<!--]--></p><p><!--[-->At some point, I thought that there must be a better way, so I started using Nuxt.js as Static Site Generator. Now no Node server was involved anymore, only plain HTML files that were served by the VPS. Not too bad, but… why do I need a <strong><!--[-->server at all<!--]--></strong> for managing a simple static page? Correct, I <strong><!--[-->do not!<!--]--></strong> But more about that in a second.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Jamstack?<!--]--></a><!----></li><li><a><!--[-->Why going Jamstack<!--]--></a><ul><!--[--><li><a><!--[-->Simplicity<!--]--></a><!----></li><li><a><!--[-->Security<!--]--></a><!----></li><li><a><!--[-->Performance<!--]--></a><!----></li><li><a><!--[-->Cost<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Nuxt as Static Site Generator<!--]--></a><!----></li><li><a><!--[-->Netlify<!--]--></a><!----></li><li><a><!--[-->Experience reports<!--]--></a><ul><!--[--><li><a><!--[-->lichter.io<!--]--></a><!----></li><li><a><!--[-->thanks.lichter.io<!--]--></a><!----></li><li><a><!--[-->blog.lichter.io<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->When not to go for Jamstack<!--]--></a><!----></li><li><a><!--[-->Netlify features I’m missing and why I still use Cloudflare<!--]--></a><ul><!--[--><li><a><!--[-->Brotli<!--]--></a><!----></li><li><a><!--[-->Analytics<!--]--></a><!----></li><li><a><!--[-->Essentially Broken HTTP2<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Jamstack?<!--]--></a></h2><p><!--[-->So, the title of the article is “Going Jamstack with Netlify and Nuxt 2”. But you might ask yourself: “What the heck is Jamstack?”. Fair enough! Jamstack is a new and modern way of web development based on <strong><!--[-->J<!--]--></strong>avascript, <strong><!--[-->A<!--]--></strong>PIs and <strong><!--[-->M<!--]--></strong>arkup.<!--]--></p><p><!--[-->A Jamstack project will be <em><!--[-->pre-built<!--]--></em> at deploy time, mostly with the help of a Static Site Generator. The markup itself is static and won’t be dynamically created or modified by the server. All the dynamics will be handled by client-side Javascript, and it doesn’t matter which framework or library you use for that.<!--]--></p><p><!--[-->Last but not least: All page interactions, like submitting forms or retrieving more content, are handled through APIs that are called via HTTP(S). Most of the time, microservice-based APIs or serverless functions are used in combination with the Jamstack approach.<!--]--></p><p><!--[-->Further information about Jamstack can be found on the official <a><!--[--><!--[-->Jamstack.org<!--]--><!--]--></a> page.<!--]--></p><h2><a><!--[-->Why going Jamstack<!--]--></a></h2><p><!--[-->Okay, so far so good. Now you might have another question: “Why should I go for Jamstack?”. There are <em><!--[-->four main reasons<!--]--></em> in my opinion.<!--]--></p><h3><a><!--[-->Simplicity<!--]--></a></h3><p><!--[-->As mentioned briefly in the introduction, you don’t need a server anymore to host your website. Instead, you can use platforms like <a><!--[--><!--[-->GitHub Pages<!--]--><!--]--></a> or <a><!--[--><!--[-->Netlify<!--]--><!--]--></a> and let them grapple with the underlying infrastructure and maintenance. No more server management!<!--]--></p><p><!--[-->It gets even better: No more difficult deploys as well. As the markup will be pre-built you could set up webhooks to re-build your website when data changes, e.g. when you update a blog post.<!--]--></p><h3><a><!--[-->Security<!--]--></a></h3><p><!--[-->Guess how often websites are being targeted by hackers? A <a><!--[--><!--[-->Forbes article<!--]--><!--]--></a> from 2013 shows that back then, more than 30,000 Wordpress pages were hacked <strong><!--[-->every day<!--]--></strong>, which is an insane number. With Jamstack, you decrease your attack surfaces a lot because your site isn’t served by a <em><!--[-->server<!--]--></em> anymore. Sure, you still have the APIs as possible targets but as they are <em><!--[-->decoupled<!--]--></em> and <em><!--[-->isolated<!--]--></em> from each other, at least most of the time, it’ll give the attacker a harder time, too.<!--]--></p><p><!--[-->No system is 100% secure but with Jamstack, things get less attractive for potential adversaries by default. And even if you are using a content management system like Wordpress through an API to populate your upcoming website: Ideally<span>^1</span> you wouldn’t expose your API at all because you could prefetch all the data needed and persist them as JSON files.<!--]--></p><h3><a><!--[-->Performance<!--]--></a></h3><p><!--[-->As your entire page is purely consisting of static assets like HTML documents, Javascript files and images, you should put all your files on a Content Delivery Network (CDN), so they are present on servers all across the world. This will reduce the “Time to first Byte” for your users because they don’t have to connect to the server your page is hosted, which could be at the other end of the world, or at least in another continent. Instead, the users will retrieve the data from their nearest CDN server. Netlify has their own CDN in place, while <a><!--[--><!--[-->Cloudflare<!--]--><!--]--></a> is another great CDN I’m using.<!--]--></p><p><!--[-->Also, because your page is re-built every time you change something, you can always invalidate the cache <strong><!--[-->directly<!--]--></strong>. This is a huge win because cache-invalidation is actually a hard thing as some of you probably experienced already.<!--]--></p><p><!--[-->Last but not least, don’t forget one thing: Static HTML is faster than, or at least as fast as, dynamic HTML returned from your server.<!--]--></p><h3><a><!--[-->Cost<!--]--></a></h3><p><!--[-->Jamstack also means cutting costs in most of the cases. No server means no server fees and also no server management needed. If you host your page, Netlify, this is <strong><!--[-->free<!--]--></strong> for a 100GB bandwidth soft limit per month. Also you can possibly reduce the resources needed for your APIs by transforming them into serverless functions.<!--]--></p><h2><a><!--[-->Nuxt as Static Site Generator<!--]--></a></h2><p><!--[-->Most of you might know Nuxt.js as “the framework on top of Vue that can do server-side rendering”. For those of you who don’t know Nuxt.js at all yet, make sure to check out the <a><!--[--><!--[-->official website<!--]--><!--]--></a>. But Nuxt.js is <strong><!--[-->way more than that<!--]--></strong>.<!--]--></p><p><!--[-->Sure, server-side rendering (SSR), is one part of it, but being a Static Site Generator is another. That means, it will take a “snapshot” of your pages and export them as HTML. I often describe it as the best of both worlds between a traditional single page application and a server-side-rendered project. While keeping the search engine optimization benefits provided by the HTML, you don’t need a Nuxt.js instance running all the time.<!--]--></p><p><!--[-->To generate the page, all you have to do is to run </p><div><span></span><!--[-->nuxt generate<!--]--><!----></div>. A <div><span></span><!--[-->dist<!--]--><!----></div> folder will appear with the HTML inside.<!--]--><p></p><h2><a><!--[-->Netlify<!--]--></a></h2><p><!--[-->Before writing about how I moved three of my websites over to Netlify and Jamstack and what I experienced during that process, I want to give a rough overview of what Netlify actually is, what they are doing and why I chose them.<!--]--></p><p><!--[-->Netlify is an all-in-one platform for hosting static websites and a pioneer of the Jamstack tech. With Netlify, you can deploy your page in under a minute. No joke! They provide automatic deployments after commits to your Git repository or via webhooks, deploy previews for Pull Requests, form handling, serverless functions, A/B testing through different branches, one-click SSL, a CDN, a managed DNS and that all with a generous free tier.<!--]--></p><h2><a><!--[-->Experience reports<!--]--></a></h2><h3><a><!--[-->lichter.io<!--]--></a></h3><p><!--[-->My portfolio page was the first one I moved to Jamstack, as said in the beginning, and moving it to Netlify was a breeze. I switched to the </p><div><span></span><!--[-->nuxt generate<!--]--><!----></div> command in my application to export my current Nuxt.js page as HTML files, connected my GitHub repository to Netlify, set up the correct build command and hit deploy. ? Tada, the page had been deployed! Now all I had left to do was pointing the domain to the correct spot and the new Jamstack-based page was up and running.<!--]--><p></p><h3><a><!--[-->thanks.lichter.io<!--]--></a></h3><p><!--[-->With my <a><!--[--><!--[-->donation page<!--]--><!--]--></a> it was not <em><!--[-->that<!--]--></em> easy because I had to think about how to manage the “donate” feature (using the Stripe API). Setting up a server just for that one function would be possible but a waste of resources. Why not giving Netlify Functions, a convenient wrapper around AWS Lambdas, a shot? As the code for the donation logic itself was always kind of “isolated”, transforming it into a serverless function was no big deal. After the “standard” procedure, converting the function, changing the URLs and testing it, thanks.lichter.io was ready as well.<!--]--></p><h3><a><!--[-->blog.lichter.io<!--]--></a></h3><p><!--[-->The last experience report is about <em><!--[-->this blog<!--]--></em>. When I wrote the first lines of code for it I already puzzled my head over which approach I should take. Markdown files and a static blog or powered by an API? I decided for the latter because I wanted to have the option for more dynamics, like showing a “Trending Posts” section based on Google Analytics data. I built the API with Laravel (yes, PHP) and the frontend with Nuxt.js as you could already guess. The blog ran on server-side rendered for a long time because the Markdown coming from the API had to be parsed “on the fly”.<!--]--></p><p><!--[-->But the more I thought about in the last weeks, the more I was convinced that a Jamstack approach would work here as well. So I started to fiddle around. The transition took a bit longer than for the other pages but that was because of the size of the project and the dynamic data. What I did, in the end, was to set up webhooks that triggered a page rebuild when changing one of my articles or updating trending posts. Furthermore, I had to change my pagination from a query string based approach (</p><div><span></span><!--[-->?page=2<!--]--><!----></div>) to a more URL-based approach (<div><span></span><!--[-->/page/2<!--]--><!----></div>) as generated pages can’t take query strings into account very well. Besides that, there were no complications.<!--]--><p></p><h2><a><!--[-->When not to go for Jamstack<!--]--></a></h2><p><!--[-->I haven’t ported all my projects to the Jamstack approach. Some of them, including <a><!--[--><!--[-->Brotli.pro<!--]--><!--]--></a>, are relying too much on dynamic data and their near-real-time availability but still need the SEO benefits. In case you have a page where data could change every few seconds (or even faster) and where you need good SEO, I would not suggest going for Jamstack for that particular project. This does depend heavily on the situation and use case though.<!--]--></p><h2><a><!--[-->Netlify features I’m missing and why I still use Cloudflare<!--]--></a></h2><p><!--[-->Though I switched over to Netlify, I still use Cloudflare in front of most of my pages. The Netlify CDN is excellent but Cloudflare provides a few features that Netlify doesn’t have at the moment and that I have to rely on.<!--]--></p><h3><a><!--[-->Brotli<!--]--></a></h3><p><!--[-->Speaking of Brotli.pro, Netlify does not support the Brotli encoding yet. This feature <a><!--[--><!--[-->is planned<!--]--><!--]--></a> and will come to Netlify in the future but there is no ETA for it yet.<!--]--></p><p><!--[-->PS: If you don’t know what Brotli is, check out <a><!--[--><!--[-->the info page<!--]--><!--]--></a><!--]--></p><h3><a><!--[-->Analytics<!--]--></a></h3><p><!--[--><strong><!--[-->Update<!--]--></strong>: Netlify Analytics is available since a few months as a <em><!--[-->paid feature<!--]--></em>.<!--]--></p><p><!--[-->While more and more people are using anti-tracking measurements and ad blockers, it’s hard to get a unique visitors number close to reality when I’d only rely on client-side analytics. Cloudflare provides them and also gives more info regarding the country of the user. Whether these numbers are “more accurate” or including bots and crawlers, I’d love to see some analytics coming for Netlify.<!--]--></p><h3><a><!--[-->Essentially Broken HTTP2<!--]--></a></h3><p><!--[-->While this is a thing that affects the majority of the CDNs right now, it’s still something I want to list. If you did not know yet, HTTP2 is essentially broken on many CDNs, including Netlify (to the time of writing). As this issue is a bit more complex, I won’t go into detail here. More information can be found on <a><!--[--><!--[-->this repository<!--]--><!--]--></a><!--]--></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->Jamstack, especially in combination with a custom API or a headless CMS** is terrific. Easy deployments, no servers to worry about and a free performance boost. Thanks to Netlify for investing time and resources in this whole architecture and style. Netlify itself as a platform is steadily growing. The current features are solid and make the whole process of getting up and running with Jamstack very friction-less. A few things are missing here and there but they are mostly related to convenience or are planned in the future.<!--]--></p><p><!--[-->If you are building your next project, be it a side project or something for a client, you should consider taking this way. And if you are looking for a neat Static Site Generator, you know <a><!--[--><!--[-->my choice<!--]--><!--]--></a> now ?<!--]--></p><p><!--[-->Thanks for reading!<!--]--></p><hr><p><!--[-->* This isn’t possible in Nuxt (without the help of modules) at the moment but we are <a><!--[--><!--[-->working on it<!--]--><!--]--></a>.<!--]--></p><p><!--[-->** Whoops, haven’t mentioned them a lot in this article. Maybe in a future one 😉<!--]--></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[My take on using Nuxt 2 with an API]]></title>
            <link>https://www.lichter.io/articles/nuxt-with-an-api/</link>
            <guid>https://www.lichter.io/articles/nuxt-with-an-api/</guid>
            <pubDate>Fri, 11 Jan 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[There are three common ways to integrate an API with Nuxt. In this blog post, I'll share my personal opinion regarding all of them, my typical procedure when deciding for one approach and the benefits and disadvantages for each of them.]]></description>
            <content:encoded><![CDATA[<p><!--[-->Hey folks! As some of you know, I’m kind of active on the <a><!--[--><!--[-->Nuxt.js Discord<!--]--><!--]--></a>. While answering questions there, one of the most common questions that’s neither covered in the docs (because you can’t generalize the answer) nor covered otherwise is: <strong><!--[-->"What approach should I take when using Nuxt 2 with my (self-built) API?"<!--]--></strong><!--]--></p><p><!--[-->This blog post will cover <em><!--[-->my take<!--]--></em> on that question. <em><!--[-->Disclaimer<!--]--></em>: My take does not mean “always the right way”. It’s just what I found out to be the best way <em><!--[-->for me<!--]--></em> ☺️
Also be aware that this post is <strong><!--[-->not about Nuxt 3<!--]--></strong>.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Three common approaches<!--]--></a><ul><!--[--><li><a><!--[-->serverMiddleware<!--]--></a><!----></li><li><a><!--[-->Using Nuxt programmatically<!--]--></a><!----></li><li><a><!--[-->Working with a standalone API<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->My take on it<!--]--></a><!----></li><li><a><!--[-->Small APIs<!--]--></a><ul><!--[--><li><a><!--[-->Static pages<!--]--></a><!----></li><li><a><!--[-->Apps with server-side rendering<!--]--></a><!----></li><li><a><!--[-->Medium-sized APIs and above<!--]--></a><!----></li><li><a><!--[-->The missing approach<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Three common approaches<!--]--></a></h2><p><!--[-->There are three main approaches when it comes to using an API with Nuxt.js. Before telling you my opinion I’ll go through each of them.<!--]--></p><ul><!--[--><li><!--[--><a><!--[--><!--[-->Leveraging Nuxt’s <div><span></span><!--[-->serverMiddleware<!--]--><!----></div><!--]--><!--]--></a><!--]--></li><li><!--[--><a><!--[--><!--[-->Using Nuxt programmatically<!--]--><!--]--></a><!--]--></li><li><!--[--><a><!--[--><!--[-->Working with a standalone API<!--]--><!--]--></a><!--]--></li><!--]--></ul><h3><a><!--[-->serverMiddleware<!--]--></a></h3><p><!--[-->Nuxt.js provides so-called </p><div><span></span><!--[-->serverMiddleware<!--]--><!----></div> which is a convenient interface to change the behavior of the underlying <em><!--[--><a><!--[--><!--[-->connect<!--]--><!--]--></a><!--]--></em> server instance. You can also early return a result so Nuxt.js doesn’t handle the path like it’d usually do but your custom <div><span></span><!--[-->serverMiddleware<!--]--><!----></div> does. With this API you can add more custom functionalities to Nuxt and even leverage Express (or any other Node.js framework) <strong><!--[-->inside Nuxt<!--]--></strong> (for example to <a><!--[--><!--[-->send emails through Nuxt.js<!--]--><!--]--></a>)!<!--]--><p></p><p><!--[-->Another use case would be a <a><!--[--><!--[-->server-side logger<!--]--><!--]--></a> which is closer to the “middleware” term you might be familiar with.<!--]--></p><p><!--[-->You don’t need another server running for it and can combine Nuxt and your features in one instance.<!--]--></p><p><!--[-->A small <strong><!--[-->example<!--]--></strong> (play around with it <a><!--[--><!--[-->here<!--]--><!--]--></a>):<!--]--></p><p><!--[--></p><div><span></span><!--[-->serverMiddleware/ok.js<!--]--><!----></div><!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  path</span><span>:</span><span> '</span><span>/test</span><span>'</span><span>,
</span></span><span><span>  handler</span><span>(</span><span>req</span><span>,</span><span> res</span><span>)</span><span> {
</span></span><span><span>    res</span><span>.</span><span>end</span><span>(</span><span>'</span><span>Everything ok!</span><span>'</span><span>)
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[--></p><div><span></span><!--[-->nuxt.config.js<!--]--><!----></div><!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  serverMiddleware</span><span>:</span><span> [
</span></span><span><span>    '</span><span>~/serverMiddleware/ok</span><span>'
</span></span><span><span>  ]
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Hitting </p><div><span></span><!--[-->/test<!--]--><!----></div> will show you <div><span></span><!--[-->'Everything ok!'<!--]--><!----></div>. The request won’t go through Nuxt.js at all (as long as it’s the initial request and not rendered client-side).<!--]--><p></p><h3><a><!--[-->Using Nuxt programmatically<!--]--></a></h3><p><!--[-->In the </p><div><span></span><!--[-->serverMiddleware<!--]--><!----></div> chapter I said it’s possible to use Express <em><!--[-->inside<!--]--></em> Nuxt. With the programmatic approach we will do <strong><!--[-->the opposite<!--]--></strong> and use <em><!--[-->Nuxt inside Express<!--]--></em> (or, as stated before, any other Node.js framework).<!--]--><p></p><p><!--[-->If you already have an Express instance running and need Nuxt only for parts of your routes (for example while transitioning from your old stack) this seems like a compelling idea!<!--]--></p><p><!--[-->Nuxt will be used as a <em><!--[-->middleware<!--]--></em> in such cases. Below is an example with Express:<!--]--></p><pre><!--[--><code><span><span>const</span><span> express</span><span> =</span><span> require</span><span>(</span><span>'</span><span>express</span><span>'</span><span>)
</span></span><span><span>const</span><span> consola</span><span> =</span><span> require</span><span>(</span><span>'</span><span>consola</span><span>'</span><span>)
</span></span><span><span>const</span><span> {</span><span> Nuxt</span><span>,</span><span> Builder</span><span> }</span><span> =</span><span> require</span><span>(</span><span>'</span><span>nuxt</span><span>'</span><span>)
</span></span><span><span>const</span><span> app</span><span> =</span><span> express</span><span>()
</span></span><span><span>const</span><span> host</span><span> =</span><span> process</span><span>.</span><span>env</span><span>.</span><span>HOST</span><span> ||</span><span> '</span><span>127.0.0.1</span><span>'
</span></span><span><span>const</span><span> port</span><span> =</span><span> process</span><span>.</span><span>env</span><span>.</span><span>PORT</span><span> ||</span><span> 3000
</span></span><span><span>
</span></span><span><span>app</span><span>.</span><span>set</span><span>(</span><span>'</span><span>port</span><span>'</span><span>,</span><span> port</span><span>)
</span></span><span><span>
</span></span><span><span>// Import and Set Nuxt.js options
</span></span><span><span>let</span><span> config</span><span> =</span><span> require</span><span>(</span><span>'</span><span>../nuxt.config.js</span><span>'</span><span>)
</span></span><span><span>config</span><span>.</span><span>dev</span><span> =</span><span> !</span><span>(</span><span>process</span><span>.</span><span>env</span><span>.</span><span>NODE_ENV</span><span> ===</span><span> '</span><span>production</span><span>'</span><span>)
</span></span><span><span>
</span></span><span><span>async</span><span> function</span><span> start</span><span>()</span><span> {
</span></span><span><span>  // Init Nuxt.js
</span></span><span><span>  const</span><span> nuxt</span><span> =</span><span> new</span><span> Nuxt</span><span>(</span><span>config</span><span>)
</span></span><span><span>
</span></span><span><span>  // Build only in dev mode
</span></span><span><span>  if</span><span> (</span><span>config</span><span>.</span><span>dev</span><span>)</span><span> {
</span></span><span><span>    const</span><span> builder</span><span> =</span><span> new</span><span> Builder</span><span>(</span><span>nuxt</span><span>)
</span></span><span><span>    await</span><span> builder</span><span>.</span><span>build</span><span>()
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  // Give nuxt middleware to express
</span></span><span><span>  app</span><span>.</span><span>use</span><span>(</span><span>nuxt</span><span>.</span><span>render</span><span>)
</span></span><span><span>
</span></span><span><span>  // Listen the server
</span></span><span><span>  app</span><span>.</span><span>listen</span><span>(</span><span>port</span><span>,</span><span> host</span><span>)
</span></span><span><span>  consola</span><span>.</span><span>ready</span><span>({
</span></span><span><span>    message</span><span>:</span><span> `</span><span>Server listening on http://</span><span>${</span><span>host</span><span>}</span><span>:</span><span>${</span><span>port</span><span>}</span><span>`</span><span>,
</span></span><span><span>    badge</span><span>:</span><span> true
</span></span><span><span>  })
</span></span><span><span>}
</span></span><span><span>start</span><span>()
</span></span></code><!--]--></pre><h3><a><!--[-->Working with a standalone API<!--]--></a></h3><p><!--[-->In case you are using another programming language for you API (say PHP, Python or Go), the only option you have is to separate the API and the Nuxt instance and to call your API via </p><div><span></span><!--[-->axios<!--]--><!----></div> or <div><span></span><!--[-->fetch<!--]--><!----></div>. Most of the time, the API and Nuxt will run on different servers then. This approach is <em><!--[-->probably<!--]--></em> the most common one as APIs are often already existing.<!--]--><p></p><h2><a><!--[-->My take on it<!--]--></a></h2><p><!--[-->Okay, there we go! My workflow and practices with APIs depend largely on:<!--]--></p><ul><!--[--><li><!--[-->Will the page be <em><!--[-->statically generated<!--]--></em> or not?<!--]--></li><li><!--[-->Is the project <em><!--[-->small<!--]--></em> or rather <em><!--[-->larger<!--]--></em> (and how will this probably change in the future)?<!--]--></li><li><!--[-->Am I building a proof of concept or similar?<!--]--></li><li><!--[-->How is the actual API I want to use / I’ve built being implemented?<!--]--></li><!--]--></ul><h2><a><!--[-->Small APIs<!--]--></a></h2><h3><a><!--[-->Static pages<!--]--></a></h3><p><!--[-->While 3 months ago, most of my pages were powered by Nuxt’s dynamic </p><div><span></span><!--[-->SSR<!--]--><!----></div> mode, I’ve converted the majority of them to static pages because they did not rely on highly dynamic data. Some examples (also open-sourced) are <a><!--[--><!--[-->developmint.de<!--]--><!--]--></a>, <a><!--[--><!--[-->thanks.lichter.io<!--]--><!--]--></a> and <strong><!--[-->this blog<!--]--></strong>. I’ll write more about the reasons, motivation, and general feelings in an upcoming blog post (so say tuned!).<!--]--><p></p><p><!--[-->When using static sites though, the API is lightweight most of the time. In such cases, I prefer to use serverless functions to isolate my API and remove the need for a server completely. A great example is a contact form endpoint, which was <a><!--[--><!--[-->previously<!--]--><!--]--></a> powered through Nuxt’s serverMiddleware but is now decoupled as a serverless function.<!--]--></p><h3><a><!--[-->Apps with server-side rendering<!--]--></a></h3><p><!--[-->For apps that are highly dynamic (like <a><!--[--><!--[-->brotli.pro<!--]--><!--]--></a>) but have a small number of API endpoints <em><!--[-->or<!--]--></em> for projects that are either proof of concepts or small-scaled I tend to use Nuxt’s built-in </p><div><span></span><!--[-->serverMiddleware<!--]--><!----></div> option. It allows me to not care about <em><!--[-->another<!--]--></em> app to set up and to route (keep in mind that I manage all my SSR-apps on my own VPS) and it also allows me fast prototyping. But as soon as the API grows, I can (more or less smooth) switch over to a standalone server (or many of them).<!--]--><p></p><h3><a><!--[-->Medium-sized APIs and above<!--]--></a></h3><p><!--[-->Most of my projects involving more complex or a non-trivial amount of API endpoints are built on </p><div><span></span><!--[-->Laravel<!--]--><!----></div> (the PHP framework). But even for those that are powered by Javascript (where I’d have all three mentioned options) I chose the same one: <strong><!--[-->A standalone API server decoupled from the Nuxt instance<!--]--></strong>.<!--]--><p></p><p><!--[-->I see some huge benefits in using this approach:<!--]--></p><ul><!--[--><li><!--[-->No single point of failure - Have the API and Nuxt instance on two different servers, ideally with a backup one for each<!--]--></li><li><!--[-->Separation of Concerns - The Nuxt instance does not “own” the business logic and DB access (and vice-versa)<!--]--></li><li><!--[-->Easier Scaling - For the API you can use functions. Even if you don’t, you can put up load balancers and add more servers more easily<!--]--></li><li><!--[-->Testing each side standalone<!--]--></li><li><!--[-->Language-agnostic - Write your API in Python, Go, PHP, Elixir, Brainf*ck, …<!--]--></li><!--]--></ul><p><!--[-->When using a standalone API (and having control over the server), please do not forget to set up a reverse-proxy, like NGINX, in front of your Nuxt instance and API, so you can:<!--]--></p><ul><!--[--><li><!--[-->proxy the API to <div><span></span><!--[-->/api<!--]--><!----></div> or whatever route you want to avoid CORS issues (use the official <a><!--[--><!--[-->proxy module<!--]--><!--]--></a> in dev mode to mimic the behavior)<!--]--></li><li><!--[-->add compression and caching headers<!--]--></li><li><!--[-->enable HTTPS, HTTP2, and HTTPS-only-connections<!--]--></li><!--]--></ul><p><!--[-->(and so on). As I’m hosting all my SSR-based apps and APIs on my own servers, this is a crucial point for me and improves perf <em><!--[-->a lot<!--]--></em> if done right.<!--]--></p><h3><a><!--[-->The missing approach<!--]--></a></h3><p><!--[-->Some of you might have noticed that I use only two out of the three ways I introduced. The one that is missing is <em><!--[-->using Nuxt programmatically<!--]--></em>. But why is that the case?<!--]--></p><p><!--[-->Well, because I do not see any advantage in it. Quite the opposite! To me, it feels a bit awkward mixing Nuxt and a framework like Express. For example, because you can’t use the ESM syntax (import/export) in your </p><div><span></span><!--[-->nuxt.config.js<!--]--><!----></div> out of the box or that you have to orchestrate the Nuxt Builder on your own.<!--]--><p></p><p><!--[-->Especially newcomers could easily come to wrong conclusions (like that Express will handle <em><!--[-->all<!--]--></em> route calls, even when the app is past the SSR step and behaves like a traditional SPA). Also, there is no “common interface” to communicate between the underlying framework and Nuxt (which doesn’t mean it’s impossible).<!--]--></p><p><!--[-->There <em><!--[-->are<!--]--></em> some exceptions where this combination does make, for example when using Nuxt.js with AWS or in another serverless context. If you know more, please <a><!--[--><!--[-->tweet me<!--]--><!--]--></a>!<!--]--></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[--><strong><!--[-->You can’t go wrong<!--]--></strong> with using a standalone API. It could mean a bit more effort to configure all the things but it will be worth it in the long run. If you need a way to bootstrap your app fast (eg. working on an MVP), and want to use Javascript in the backend too, utilize Nuxt’s </p><div><span></span><!--[-->serverMiddleware<!--]--><!----></div> capacities.<!--]--><p></p><p><!--[-->But as usual, I suggest <em><!--[-->try out<!--]--></em> all approaches once to see what fits best <strong><!--[-->for you<!--]--></strong>. If you’ve found out the approach you like most, feel free to check out how to <a><!--[--><!--[-->organize APIs in Nuxt.js<!--]--><!--]--></a> properly!<!--]--></p><p><!--[-->Did I miss something or do you have a question? Tweet me at <a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io).<!--]--></p><p><!--[-->Hope this article cleared up the ways to integrate an API with Nuxt.js!<!--]--></p><style>html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sxA9i, html code.shiki .sxA9i{--shiki-default:#4C9A91}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2018 - My Recap]]></title>
            <link>https://www.lichter.io/articles/2018-my-recap/</link>
            <guid>https://www.lichter.io/articles/2018-my-recap/</guid>
            <pubDate>Sun, 06 Jan 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[Wow! What. A. Year. 2018 lies in the past and I felt that this was by far the most productive year I had. On the other hand, I’m somewhat certain that this is not the end of the road...]]></description>
            <content:encoded><![CDATA[<p><!--[-->Wow! What. A. Year.<!--]--></p><p><!--[-->2018 lies in the past and I felt that this was <em><!--[-->by far<!--]--></em> the most productive year I had. On the other hand, I’m somewhat certain that this is not the end of the road. Though it was a great year, there are always things one could have done better, more professional, faster or just not at all.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Personal Life<!--]--></a><!----></li><li><a><!--[-->Nuxt.js (and friends)<!--]--></a><!----></li><li><a><!--[-->Blogging<!--]--></a><!----></li><li><a><!--[-->Twitter<!--]--></a><!----></li><li><a><!--[-->Speaking<!--]--></a><!----></li><li><a><!--[-->Developmint<!--]--></a><!----></li><li><a><!--[-->Shipping<!--]--></a><!----></li><li><a><!--[-->Summary and Goals<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Personal Life<!--]--></a></h2><p><!--[-->The last year brought some bigger changes to my life. I started a half-year long internship at Xilinx Ireland, beginning at the end of summer. But before, I had to move out of my current flat which was on the 5th floor and there was no elevator. (PS: can't recommend) and <em><!--[-->also<!--]--></em> to take some very important exams in university. Though it was a little stressful because the dates were all close together, the exams were very “intense” (read: a lot of topics to learn and not much time to learn) and work for my own company was also always calling, I managed the situation somehow and I’d say, not too badly. I passed the exams, moved out on time, finished a large contract project and then went abroad. After roughly 4 months in Dublin, I must admit that I love the warmth and friendliness of the people, the city, the landscape but definitely not the weather.<!--]--></p><p><!--[-->Working full-time and not being self-employed - and not being a student in the meantime - forced me to re-schedule almost everything because time is even more rare now. Luckily I still have time for my girlfriend (who isn’t abroad with me), open source, going to the gym - hopefully regularly again, friends, my own company and to explore the country.<!--]--></p><p><!--[-->Speaking of open source…<!--]--></p><h2><a><!--[-->Nuxt.js (and friends)<!--]--></a></h2><p><!--[-->I started using Nuxt already back in December 2017 when I decided to rebuild my <a><!--[--><!--[-->personal page<!--]--><!--]--></a> with it. However, the relation between Nuxt.js (the framework <em><!--[-->and<!--]--></em> the team) and me really evolved in 2018. With small steps, like showcasing my page and creating some bug reports in the first months, I was able to make first contributions to the Nuxt.js ecosystem in march. For example to:<!--]--></p><ul><!--[--><li><!--[-->the Nuxt <a><!--[--><!--[-->PWA Module<!--]--><!--]--></a><!--]--></li><li><!--[-->to my favorite CSS Framework, TailwindCSS to <a><!--[--><!--[-->make it usable with Nuxt<!--]--><!--]--></a>,<!--]--></li><li><!--[--><a><!--[--><!--[-->vue-meta<!--]--><!--]--></a> (used by Nuxt.js for the head tag management)<!--]--></li><li><!--[-->the (now somewhat neglected) <a><!--[--><!--[-->auth module<!--]--><!--]--></a><!--]--></li><li><!--[-->and several more…<!--]--></li><!--]--></ul><p><!--[-->This was followed by an invite to the Nuxt Community GitHub organization and the Nuxt.js Slack (PS: we use Discord now!). I really felt honored and had the chance to contribute even more, which I did. In April, I released my first two nuxt modules for <a><!--[--><!--[-->redirects<!--]--><!--]--></a> and <a><!--[--><!--[-->RSS/ATOM/JSON feeds<!--]--><!--]--></a> (out of 8 modules this year). Both are still maintained mainly by me and have over 10k total downloads <em><!--[-->each<!--]--></em>. I’ve also created my first open-source Vue components:<!--]--></p><ul><!--[--><li><!--[--><a><!--[--><!--[-->vue-if-bot<!--]--><!--]--></a> to show or hide sth. for crawlers (useful to hide cookie banners)<!--]--></li><li><!--[--><a><!--[--><!--[-->vue-link<!--]--><!--]--></a> for unifying external and internal links<!--]--></li><li><!--[--><a><!--[--><!--[-->vue-next-level-scroll<!--]--><!--]--></a> for the best scroll experience<!--]--></li><!--]--></ul><p><!--[-->These components haven’t been adopted in large scale but that’s fine. I’ve created them to abstract logic that I had to use in a hand full of projects. Because nobody likes to copy and paste component code over and over again.<!--]--></p><p><!--[-->After passing my exams and before moving to Dublin I spent a lot of time with Contributions to Nuxt. It was really addictive to propose new changes, squash bugs and talk to the core team directly. Alone in August, I was able to propose 19 PRs of which were 14 merged into the Nuxt.js core.<!--]--></p><p><!--[-->And then there was the Vue.js London conference. Again, thanks to the Nuxt.js team (and my manager at Xilinx who allowed me to go) I was able to attend the conference. And not only that, my ticket was funded by the open collective money from Nuxt to thank me for my steady contributions. I was incredibly happy when Sebastien told me that, especially because I then would meet the Nuxt brothers in person.<!--]--></p><p><!--[-->It happened. 10 minutes before the legendary live-release (which I did not know of) presentation at Vue.js London I met these two awesome guys and they said: “Oh, you know what? You are in the slides. <strong><!--[-->Welcome to the Core Team!<!--]--></strong>”<!--]--></p><p><!--[-->That was definitely one of the most significant moments in 2018. <strong><!--[-->No joke<!--]--></strong>. It meant so much to me (and it still does)!<!--]--></p><p><!--[-->The conference itself was my first Vue conference and it <em><!--[-->was a blast<!--]--></em>. So many interesting, clever and kind people in one place. New connections and new friends. Very well-prepared and informative talks and a nice after-party. This won’t be my last conference, no doubts!<!--]--></p><p><!--[-->Nuxt has evolved since then as well. More features, smart ideas, and also a more <em><!--[-->thoughtful<!--]--></em> progress. It was the <a><!--[--><!--[-->4th fastest growing<!--]--><!--]--></a> open source project in 2018. And my prediction for 2019 is that Nuxt will grow further, will evolve further, will mature further.<!--]--></p><p><!--[--><em><!--[-->PS<!--]--></em>: Becoming a core maintainer isn’t impossible at all, even if you don’t have decades of experience. If you are willing to invest time, eager to learn new things, ready to help people and, if you want, available to take a few responsibilities you just have to <em><!--[-->show<!--]--></em> that you are interested in it. That means you can start helping out where help is needed: Triaging issues, answering questions on several platforms, improving the docs, and so on. Contributing to the code itself is important but not everything!<!--]--></p><h2><a><!--[-->Blogging<!--]--></a></h2><p><!--[-->One of my Goals for 2018 was to finally start blogging! I even had a list with topics that got longer and longer but I had no time to actually build the blog. Of course, I wanted to build it completely on my own with my favorite tech stack. In Summer I finally had the time to do so and at the end of August, the first blog posts had been released.<!--]--></p><p><!--[-->In 2018 I wrote “just” 7 blog posts, which is barely more than 1 per month. But I’m quite relieved that almost all the feedback was positive. People told me that the blog posts helped them and that they learned new things. Exactly what I wanted to achieve! With more than 7.000 unique visitors just in December, this goal is also validated (thought visitor count is not the only metric which is important here).<!--]--></p><h2><a><!--[-->Twitter<!--]--></a></h2><p><!--[-->I haven’t been using Twitter <em><!--[-->at all<!--]--></em> for a longer time (until 2016). Thanks to my friend and colleague Max Langer I finally joined Twitter and regretted it to be late to the party a few weeks after. Since then I was most of the time a silent reader that likes stuff and throws in a few comments here or there.<!--]--></p><p><!--[-->Since I started to dig into Nuxt (and had no blog or another platform) I released small tips regarding the framework. Thanks to them I achieved the 500 follower milestone in 2018. Here applies the same as for my blog post. I’m happy if these tips help people and if people like them (and spread them of course ☺️)<!--]--></p><h2><a><!--[-->Speaking<!--]--></a></h2><p><!--[-->Thankfully I had the chance to gave a few talks in my university at the beginning of the year. These were about <a><!--[--><!--[-->Functional Programming<!--]--><!--]--></a> and <a><!--[--><!--[-->why PHP isn’t dead<!--]--><!--]--></a> (yeah, <strong><!--[-->it is not<!--]--></strong>).<!--]--></p><p><!--[-->In May I <a><!--[--><!--[-->held another PHP talk<!--]--><!--]--></a> at the local PHP Usergroup (thanks to PHPUGDD for having me!).<!--]--></p><p><!--[-->After arriving in Dublin I was on the look for Vue.js meetups and was glad that there was one group that did recurring meetups. The first meetup with them was terrific! A smaller group with, again, very welcoming people. We all introduced each other and went for a beer after two great talks and delicious pizza. Of course, I wanted to give something back to the community as well. So it came that I held the 5th and last talk of the year, an <a><!--[--><!--[-->Introduction to Nuxt.js<!--]--><!--]--></a> in November. Nothing went wrong, the questions during the Q&amp;A were sophisticated and the people were happy. What a nice end (with regards to speaking) for that year!<!--]--></p><p><!--[-->PS: If you are in Ireland, please come around and say hi at the meetup! We are always looking for speakers too ☺️ PPS: The new <a><!--[--><!--[-->VueJS Dublin website<!--]--><!--]--></a> will be made with Nuxt too. But it’s still WIP.<!--]--></p><h2><a><!--[-->Developmint<!--]--></a></h2><p><!--[-->My own company (that I lead together with Max I mentioned above) turned three in August. I can’t really tell a lot because most contracts are under an NDA or the results already published on our <a><!--[--><!--[-->company page<!--]--><!--]--></a> (which has been rebuilt with Nuxt too). 2018 was a great year for the company. Though I’m abroad I still do some work when I find the time. Since I also offer hourly billed Nuxt.js consulting when I can, this is no wonder.<!--]--></p><h2><a><!--[-->Shipping<!--]--></a></h2><p><!--[-->Besides contract work, I also shipped a few things. Some secretly, some quite public.<!--]--></p><p><!--[-->As you know, I built my blog and rebuilt my personal page and my company's page. What also happened is that I built a small donation page for me (because it was requested by people who wanted to donate). A great chance to fiddle around with Stripe, build things in the open and create another learning repository that includes a Nuxt project. Tada, <a><!--[--><!--[-->thanks.lichter.io<!--]--><!--]--></a> has been built.<!--]--></p><p><!--[-->There is also <a><!--[--><!--[-->Brotli.Pro<!--]--><!--]--></a> which is still in testing phase but is publicly available (and was shipped secretly). With it, you can test if your website properly uses Brotli compression or not. This can make a huge size difference for your files! I’m still working on some content for the page. This one isn’t open-sourced (like my blog isn’t [yet?]).<!--]--></p><h2><a><!--[-->Summary and Goals<!--]--></a></h2><p><!--[-->Wow! This post is now way longer than expected. Okay, quick: Here are my goals:<!--]--></p><ul><!--[--><li><!--[--><strong><!--[-->Blogging more frequently<!--]--></strong> (Blogging has become one of my favorite tasks. I like writing more and more and there are still so many things to write about)<!--]--></li><li><!--[-->Keeping up the <strong><!--[-->gym routine throughout the year<!--]--></strong> (I don’t want to get too lazy again, especially when I’m back in Germany)<!--]--></li><li><!--[-->Improve my <strong><!--[-->organization<!--]--></strong> - More lists, written stuff, timeboxing!<!--]--></li><li><!--[-->Look into <strong><!--[-->Elixir<!--]--></strong> (I like functional programming and Elixir looks quite nice. Why not trying it out?)<!--]--></li><li><!--[-->Complete another <strong><!--[-->awesome Hacktoberfest<!--]--></strong> (This year I opened 56 PRs in October and 54 were merged. I want to keep that up)<!--]--></li><!--]--></ul><p><!--[-->That’s it, folks! Thanks for reading and a happy 2019 with a lot of success to you!<!--]--></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Selectively enable SSR or SPA mode in a Nuxt.js 2 app]]></title>
            <link>https://www.lichter.io/articles/nuxt2-dynamic-ssr-spa/</link>
            <guid>https://www.lichter.io/articles/nuxt2-dynamic-ssr-spa/</guid>
            <pubDate>Sat, 29 Dec 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[SSR comes with certain caveats, including no access to APIs like the local storage on server-side. But what if you could enable SSR only for pages where SEO is needed and use the "traditional" SPA mode elsewhere? You can! Learn how in this article.]]></description>
            <content:encoded><![CDATA[<blockquote><!--[--><p><!--[-->This article is written for <strong><!--[-->Nuxt 2<!--]--></strong>.<!--]--></p><p><!--[-->If you are looking for the <strong><!--[-->Nuxt 3<!--]--></strong> version of this article, please <a><!--[--><!--[-->follow this link to the updated Nuxt 3 version<!--]--><!--]--></a>.<!--]--></p><!--]--></blockquote><p><!--[-->Hey Nuxt friends! As you might already have experienced, SSR <a><!--[--><!--[-->comes with certain caveats<!--]--><!--]--></a>, including no access to browser-specific APIs, like the local storage, on the server-side. But what if you could enable SSR only for pages where SEO is needed and use the “traditional” SPA mode elsewhere, say in a “member area” in your Nuxt 2 application.<!--]--></p><p><!--[-->Guess what? <strong><!--[-->You can do that!<!--]--></strong> In the following post I’ll show you how to selectively disable SSR or SPA mode based on the page url.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->The Nuxt Source Code<!--]--></a><!----></li><li><a><!--[-->Implementation<!--]--></a><!----></li><li><a><!--[-->Wrapping up<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->The Nuxt Source Code<!--]--></a></h2><p><!--[-->Before going into detail on how to enable SSR selectively we should look into the Nuxt.js source code to see how Nuxt makes such a distinction possible. The responsible snippet can be found in the <a><!--[--><!--[-->vue-renderer package<!--]--><!--]--></a>. Let’s dissect it one line after each other!<!--]--></p><ol><!--[--><li><!--[-->Extract <div><span></span><!--[-->req<!--]--><!----></div> and <div><span></span><!--[-->res<!--]--><!----></div> (the request and response object from the request to the server) from the context object.<!--]--></li><!--]--></ol><pre><!--[--><code><span><span>const</span><span> {</span><span> req</span><span>,</span><span> res</span><span> }</span><span> =</span><span> context
</span></span></code><!--]--></pre><ol><!--[--><li><!--[-->If <div><span></span><!--[-->context.spa<!--]--><!----></div> (might have been set through other internals before rendering pages) or <div><span></span><!--[-->res.spa<!--]--><!----></div> (can be modified otherwise!) is truthy, treat the page that’ll be rendered as <strong><!--[-->SPA<!--]--></strong>.<!--]--></li><!--]--></ol><pre><!--[--><code><span><span>const</span><span> spa</span><span> =</span><span> context</span><span>.</span><span>spa</span><span> ||</span><span> (</span><span>res</span><span> &amp;&amp;</span><span> res</span><span>.</span><span>spa</span><span>)
</span></span></code><!--]--></pre><ol><!--[--><li><!--[-->In case SSR is <em><!--[-->disabled<!--]--></em> or the page <em><!--[-->should be treated as SPA<!--]--></em>, only load metadata and render the page as SPA (with JavaScript files but no HTML included) through an <em><!--[-->early return<!--]--></em>.<!--]--></li><!--]--></ol><pre><!--[--><code><span><span>if</span><span> (</span><span>!</span><span>this</span><span>.</span><span>SSR</span><span> ||</span><span> spa</span><span>)</span><span> {
</span></span><span><span>  const</span><span> {</span><span>/* ... */</span><span>}</span><span> =</span><span> await</span><span> this</span><span>.</span><span>renderer</span><span>.</span><span>spa</span><span>.</span><span>render</span><span>(</span><span>context</span><span>)
</span></span><span><span>  const</span><span> html</span><span> =</span><span> this</span><span>.</span><span>renderTemplate</span><span>(</span><span>/* ... */</span><span>)
</span></span><span><span>  return</span><span> {</span><span> html</span><span>,</span><span> getPreloadFiles</span><span>:</span><span> this</span><span>.</span><span>getPreloadFiles</span><span>.</span><span>bind</span><span>(</span><span>this</span><span>,</span><span> {</span><span> getPreloadFiles</span><span> })</span><span> }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->And that’s all the magic ☺️<!--]--></p><h2><a><!--[-->Implementation<!--]--></a></h2><p><!--[-->After looking into the source code we found out that all we have to do is to set </p><div><span></span><!--[-->res.spa<!--]--><!----></div> for the pages where we want to <strong><!--[-->disable server side rendering<!--]--></strong>. This only has to happen on the server-side because a Nuxt app will behave like a traditional SPA on client-side anyway. If we think about server-side only manipulation, the first thing that should come into our minds should be <div><span></span><!--[-->serverMiddleware<!--]--><!----></div> (see also: <a><!--[--><!--[-->Sending Emails Through Nuxt.js via serverMiddleware<!--]--><!--]--></a>). Using them comes with two major benefits:<!--]--><p></p><ol><!--[--><li><!--[--><div><span></span><!--[-->serverMiddleware<!--]--><!----></div> are a concept that is directly provided through the framework (no 3rd party libs needed)<!--]--></li><li><!--[-->We can freely customize our selection logic.<!--]--></li><!--]--></ol><p><!--[-->A minimalist implementation would look like this:<!--]--></p><pre><!--[--><code><span><span>export</span><span> default</span><span> function</span><span>(</span><span>req</span><span>,</span><span> res</span><span>,</span><span> next</span><span>)</span><span> {
</span></span><span><span>  const</span><span> paths</span><span> =</span><span> [</span><span>'</span><span>/</span><span>'</span><span>,</span><span> '</span><span>/a</span><span>'</span><span>]
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>paths</span><span>.</span><span>includes</span><span>(</span><span>req</span><span>.</span><span>originalUrl</span><span>))</span><span> {
</span></span><span><span>    // Will trigger the "traditional SPA mode"
</span></span><span><span>    res</span><span>.</span><span>spa</span><span> =</span><span> true
</span></span><span><span>  }
</span></span><span><span>  // Don't forget to call next in all cases!
</span></span><span><span>  // Otherwise, your app will be stuck forever :|
</span></span><span><span>  next</span><span>()
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->To see a working example directly, you can <a><!--[--><!--[-->check out the CodeSandbox<!--]--><!--]--></a>. Be aware that it runs in </p><div><span></span><!--[-->dev<!--]--><!----></div> mode so the difference between SPA and SSR isn’t that huge but still distinguishable via <div><span></span><!--[-->process.server<!--]--><!----></div>.<!--]--><p></p><h2><a><!--[-->Wrapping up<!--]--></a></h2><p><!--[-->You might ask yourself: <strong><!--[-->Do I need to selectively switch between the modes?<!--]--></strong> The answer is most of the time: <strong><!--[-->No<!--]--></strong>. But there might be some situation where you want to avoid wrapping a lot of page contents in </p><div><span></span><!--[-->&lt;ClientOnly&gt;<!--]--><!----></div> tags (especially for dashboards or member areas) that are part of your SEO-heavy app. It might be a niche but it has it’s use cases.<!--]--><p></p><p><!--[-->What did you use the selective SPA/SSR for? Please tweet me at <a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io).<!--]--></p><p><!--[-->I hope you enjoyed the article and the small Nuxt source code dive. If this is the case, I’d gladly ask you to <strong><!--[-->spread the word<!--]--></strong>!<!--]--></p><style>html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .sXjYR, html code.shiki .sXjYR{--shiki-default:#C99076}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Organize and decouple your API calls in Nuxt 2]]></title>
            <link>https://www.lichter.io/articles/nuxt-api-call-organization-and-decoupling/</link>
            <guid>https://www.lichter.io/articles/nuxt-api-call-organization-and-decoupling/</guid>
            <pubDate>Thu, 22 Nov 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[As your Nuxt app grows, so does your backend. With time, your API evolves from a hand full of endpoints into an enormous jungle of resources. And you want to stay the king of the jungle! Learn to organize and abstract your API resources in this post.]]></description>
            <content:encoded><![CDATA[<p><!--[-->As your Nuxt app grows, so does your backend. With time, your API evolves from a hand full of endpoints into an enormous jungle of resources. And you want to stay the king of the jungle! That means not losing track of the different (RESTful) endpoints and organizing them properly. Imagine you want to rename a resource called </p><div><span></span><!--[-->images<!--]--><!----></div> to <div><span></span><!--[-->photos<!--]--><!----></div>. Nothing could be worse than doing a find and replace and using all your brainpower to not miss a call and avoid renaming other variables by accident. Or think about when you’d have to add another header for your Authentication.<!--]--><p></p><p><!--[-->So, a decent organization of your API calls in your frontend is necessary to stay sane. But how do we do that with Nuxt 2?<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->The two sides of the Nuxt coin<!--]--></a><!----></li><li><a><!--[-->The Nuxt axios module<!--]--></a><!----></li><li><a><!--[-->Abstracting our REST API<!--]--></a><!----></li><li><a><!--[-->Dependency Injection to the rescue<!--]--></a><!----></li><li><a><!--[-->Generalize our implementation<!--]--></a><!----></li><li><a><!--[-->Leverage the power of Nuxt.js plugins<!--]--></a><ul><!--[--><li><a><!--[-->Passing in the axios instance<!--]--></a><!----></li><li><a><!--[-->Inject it<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Use it<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->The two sides of the Nuxt coin<!--]--></a></h2><p><!--[-->With Nuxt it seems to be bit more complicated to properly organize your API requests because you have to think about the <em><!--[-->server side<!--]--></em> in addition to the client side. You will probably retrieve data from your backend in </p><div><span></span><!--[-->asyncData<!--]--><!----></div> or <div><span></span><!--[-->fetch<!--]--><!----></div> methods, in the Vuex store or simply inside a Vue component. That means, our solution has to be accessible in all these <em><!--[-->contexts<!--]--></em> ?<!--]--><p></p><h2><a><!--[-->The Nuxt axios module<!--]--></a></h2><p><!--[-->If you are not using the <a><!--[--><!--[-->official Nuxt axios module<!--]--><!--]--></a> by now, you should really switch over soon (read: <strong><!--[-->now<!--]--></strong>). Instead of importing axios everywhere, the module provides you a convenient interface on the client-side (</p><div><span></span><!--[-->this.$axios<!--]--><!----></div> in Vue components), in the Nuxt context (<div><span></span><!--[-->ctx.$axios<!--]--><!----></div>) that is available in functions like <div><span></span><!--[-->asyncData<!--]--><!----></div> and <div><span></span><!--[-->fetch<!--]--><!----></div> and in the Vuex store (<div><span></span><!--[-->this.$axios<!--]--><!----></div> again but not in arrow functions!). Furthermore, you can set up default headers, a <div><span></span><!--[-->baseURL<!--]--><!----></div> depending on your environment and if necessary you can tweak your axios instance even more. Plus, you have a <a><!--[--><!--[-->shorthand function<!--]--><!--]--></a> for all HTTP verbs (eg. <div><span></span><!--[-->$get<!--]--><!----></div>) which lets you omit the nested <div><span></span><!--[-->data<!--]--><!----></div> key and delivers the response object on the top level.<!--]--><p></p><p><!--[-->But is that it? Of course not! We found a way to centralize our configuration but that’s just one part of the puzzle.<!--]--></p><h2><a><!--[-->Abstracting our REST API<!--]--></a></h2><p><!--[-->Now the interesting part begins. For further context, a <em><!--[-->RESTful<!--]--></em> API will be assumed. The techniques in the following paragraphs will work for most of the APIs though but have to be adapted slightly.<!--]--></p><p><!--[-->For our example, we will use parts of the <a><!--[--><!--[-->JSON Placeholder API<!--]--><!--]--></a> as it covers all HTTP verbs and is not too complex.<!--]--></p><p><!--[-->Let’s take a look at the </p><div><span></span><!--[-->posts<!--]--><!----></div> resource. What you want to do with it is:<!--]--><p></p><ul><!--[--><li><!--[--><em><!--[-->Create<!--]--></em> a post<!--]--></li><li><!--[--><em><!--[-->Show<!--]--></em> details of a specific post (eg. title and content)<!--]--></li><li><!--[-->Show an overview of (all/some) posts, also called a post <em><!--[-->index<!--]--></em> (often used on an <em><!--[-->index<!--]--></em> page as well)<!--]--></li><li><!--[--><em><!--[-->Update<!--]--></em> a post<!--]--></li><li><!--[--><em><!--[-->Delete<!--]--></em> a post<!--]--></li><!--]--></ul><p><!--[-->If we consider other resources, for example </p><div><span></span><!--[-->users<!--]--><!----></div>, the pattern will repeat:<!--]--><p></p><ul><!--[--><li><!--[--><em><!--[-->Create<!--]--></em> a user<!--]--></li><li><!--[--><em><!--[-->Show<!--]--></em> a user’s details (for example on his profile page)<!--]--></li><li><!--[-->Show a list/an <em><!--[-->index<!--]--></em> of all users (member list)<!--]--></li><li><!--[--><em><!--[-->Update<!--]--></em> a user<!--]--></li><li><!--[--><em><!--[-->Delete<!--]--></em> a user<!--]--></li><!--]--></ul><p><!--[-->With this knowledge, we can try to abstract our API with a Plain Old Javascript Object (<strong><!--[-->POJO<!--]--></strong>) by creating a method for each <em><!--[-->verb<!--]--></em>. A </p><div><span></span><!--[-->class<!--]--><!----></div> would be fine as well.<!--]--><p></p><p><!--[-->The design pattern we borrow us here is called <strong><!--[-->Repository Pattern<!--]--></strong>.<!--]--></p><p><!--[-->Let’s save this file to </p><div><span></span><!--[-->~/api/repository.js<!--]--><!----></div>.<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  create</span><span>(</span><span>payload</span><span>)</span><span> {},
</span></span><span><span>
</span></span><span><span>  show</span><span>(</span><span>id</span><span>)</span><span> {},
</span></span><span><span>
</span></span><span><span>  index</span><span>()</span><span> {},
</span></span><span><span>
</span></span><span><span>  update</span><span>(</span><span>payload</span><span>,</span><span> id</span><span>)</span><span> {},
</span></span><span><span>
</span></span><span><span>  delete</span><span>(</span><span>id</span><span>)</span><span> {}
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Okay, so we have a basic scaffold for our API abstraction though we still have to fill the methods somehow. Now the </p><div><span></span><!--[-->$axios<!--]--><!----></div> from the Nuxt axios module comes in handy. But… how can we add it to an external module? Trying to access <div><span></span><!--[-->this.$axios<!--]--><!----></div> will fail.<!--]--><p></p><h2><a><!--[-->Dependency Injection to the rescue<!--]--></a></h2><p><!--[-->Another term crosses our way: </p><div><span></span><!--[-->Dependency Injection<!--]--><!----></div>. It’s a very common pattern in object oriented languages like PHP or Java and will help us in our situation as well.<!--]--><p></p><p><!--[-->If you are <strong><!--[-->depending on a library<!--]--></strong> like axios you usually would import and use it like this:<!--]--></p><pre><!--[--><code><span><span>import</span><span> axios</span><span> from</span><span> '</span><span>axios</span><span>'
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> {
</span></span><span><span>  async</span><span> index</span><span>()</span><span> {
</span></span><span><span>    const</span><span> result</span><span> =</span><span> await</span><span> axios</span><span>.</span><span>get</span><span>(</span><span>'</span><span>...</span><span>'</span><span>)
</span></span><span><span>    return</span><span> result</span><span>.</span><span>data
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->This is fine for many use-cases, but for that one, the approach comes with several disadvantages:<!--]--></p><ul><!--[--><li><!--[-->You can’t easily swap out the implementation for another one if you want to<!--]--></li><li><!--[-->The dependency has to be known before runtime<!--]--></li><!--]--></ul><p><!--[-->While the former isn’t a huge issue in our case, the latter poses a problem for us. We <em><!--[-->know<!--]--></em> the dependency but we can’t actually </p><div><span></span><!--[-->import<!--]--><!----></div> it.<!--]--><p></p><p><!--[-->Instead, we <strong><!--[-->inject the dependency<!--]--></strong> which means we pass it as a parameter in a function (or to the constructor if you are using a </p><div><span></span><!--[-->class<!--]--><!----></div>).<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> axios</span><span> =&gt;</span><span> ({
</span></span><span><span>  async</span><span> index</span><span>()</span><span> {
</span></span><span><span>    const</span><span> result</span><span> =</span><span> await</span><span> axios</span><span>.</span><span>get</span><span>(</span><span>'</span><span>...</span><span>'</span><span>)
</span></span><span><span>    return</span><span> result</span><span>.</span><span>data
</span></span><span><span>  }</span><span>)
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Boom, <strong><!--[-->dependency injected<!--]--></strong>! Let’s fill out our original scaffold and make use of the </p><div><span></span><!--[-->$axios<!--]--><!----></div> shorthands:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> $axios</span><span> =&gt;</span><span> ({
</span></span><span><span>  index</span><span>()</span><span> {
</span></span><span><span>    return</span><span> $axios</span><span>.</span><span>$get</span><span>(</span><span>'</span><span>/posts</span><span>'</span><span>)
</span></span><span><span>  },
</span></span><span><span>  create</span><span>(</span><span>payload</span><span>)</span><span> {
</span></span><span><span>    return</span><span> $axios</span><span>.</span><span>$post</span><span>(</span><span>`</span><span>/posts</span><span>`</span><span>,</span><span> payload</span><span>)
</span></span><span><span>  },
</span></span><span><span>  show</span><span>(</span><span>id</span><span>)</span><span> {
</span></span><span><span>    return</span><span> $axios</span><span>.</span><span>$get</span><span>(</span><span>`</span><span>/posts/</span><span>${</span><span>id</span><span>}</span><span>`</span><span>)
</span></span><span><span>  },
</span></span><span><span>  update</span><span>(</span><span>payload</span><span>,</span><span> id</span><span>)</span><span> {
</span></span><span><span>    return</span><span> $axios</span><span>.</span><span>$put</span><span>(</span><span>`</span><span>/posts/</span><span>${</span><span>id</span><span>}</span><span>`</span><span>,</span><span> payload</span><span>)
</span></span><span><span>  },
</span></span><span><span>  delete</span><span>(</span><span>id</span><span>)</span><span> {
</span></span><span><span>    return</span><span> $axios</span><span>.</span><span>$delete</span><span>(</span><span>`</span><span>/posts/</span><span>${</span><span>id</span><span>}</span><span>`</span><span>)
</span></span><span><span>  }
</span></span><span><span>})
</span></span></code><!--]--></pre><h2><a><!--[-->Generalize our implementation<!--]--></a></h2><p><!--[-->Okay, so far so good. Currently, the </p><div><span></span><!--[-->posts<!--]--><!----></div> resource is still hardcoded into our methods. To increase the reusability we want to pass in the resource URL dynamically again.<!--]--><p></p><p><!--[-->Instead of adding a second parameter to our default export we will transform the function to a <em><!--[-->higher order function<!--]--></em> (a function that returns a function):<!--]--></p><pre><!--[--><code><span><span>export</span><span> default</span><span> $axios</span><span> =&gt;</span><span> resource</span><span> =&gt;</span><span> ({
</span></span><span><span>  index</span><span>()</span><span> {
</span></span><span><span>    return</span><span> $axios</span><span>.</span><span>$get</span><span>(</span><span>`</span><span>/</span><span>${</span><span>resource</span><span>}</span><span>`</span><span>)
</span></span><span><span>  },
</span></span><span><span>  // ...
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Now when we import our function somewhere, we can reuse it for many resources, but we only need to pass in the axios instance once:<!--]--></p><pre><!--[--><code><span><span>import</span><span> createRepository</span><span> from</span><span> '</span><span>~/api/repository.js</span><span>'
</span></span><span><span>
</span></span><span><span>// First, call the function with the axios object
</span></span><span><span>
</span></span><span><span>const</span><span> $axios</span><span> =</span><span> getAxiosMagicallyFromSomewhere</span><span> // we will find out how to get it further down the road
</span></span><span><span>const</span><span> repositoryWithAxios</span><span> =</span><span> createRepository</span><span>(</span><span>$axios</span><span>)
</span></span><span><span>
</span></span><span><span>// Now you can create repositories and re-use the `repositoryWithAxios` function
</span></span><span><span>
</span></span><span><span>const</span><span> postRepository</span><span> =</span><span> repositoryWithAxios</span><span>(</span><span>'</span><span>posts</span><span>'</span><span>)
</span></span><span><span>const</span><span> userRepository</span><span> =</span><span> repositoryWithAxios</span><span>(</span><span>'</span><span>users</span><span>'</span><span>)
</span></span><span><span>// ...
</span></span></code><!--]--></pre><p><!--[-->With this pattern, you don’t have to pass in the options again and again.<!--]--></p><h2><a><!--[-->Leverage the power of Nuxt.js plugins<!--]--></a></h2><p><!--[-->There we go. We have abstracted our API resources successfully and found a way to re-use that abstraction properly, but two problems are still unsolved.:<!--]--></p><ul><!--[--><li><!--[-->How can we <strong><!--[-->access<!--]--></strong> the abstraction throughout our Nuxt app?<!--]--></li><li><!--[-->How can we properly <strong><!--[-->pass in<!--]--></strong> the axios instance from the Nuxt module?<!--]--></li><!--]--></ul><p><!--[-->To solve both problems at once we utilize a <strong><!--[-->Nuxt.js plugin<!--]--></strong>. They are mostly used to add global Vue components or libraries but can be used in many other ways too. Also, they are evaluated before creating the root Vue instance.<!--]--></p><p><!--[-->Okay, let’s create a file at </p><div><span></span><!--[-->~/plugins/repository.js<!--]--><!----></div>.<!--]--><p></p><p><!--[-->If the plugin has a default export function, two parameters will be passed to that function: The <strong><!--[-->Nuxt context<!--]--></strong> and a method called </p><div><span></span><!--[-->inject<!--]--><!----></div>.<!--]--><p></p><pre><!--[--><code><span><span>import</span><span> createRepository</span><span> from</span><span> '</span><span>~/api/repository.js</span><span>'
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> (</span><span>ctx</span><span>,</span><span> inject</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  // Here we will do it
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Inside the function, we have everything we need to make our API repositories available across all relevant parts of our Nuxt app and to pass the axios instance to our construct.<!--]--></p><h3><a><!--[-->Passing in the axios instance<!--]--></a></h3><p><!--[-->Because the context is available in the plugin default export method, we can pass in the axios instance without any problems:<!--]--></p><pre><!--[--><code><span><span>import</span><span> createRepository</span><span> from</span><span> '</span><span>~/api/repository.js</span><span>'
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> (</span><span>ctx</span><span>,</span><span> inject</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  const</span><span> repositoryWithAxios</span><span> =</span><span> createRepository</span><span>(</span><span>ctx</span><span>.</span><span>$axios</span><span>)
</span></span><span><span>
</span></span><span><span>  const</span><span> repositories</span><span> =</span><span> {
</span></span><span><span>    posts</span><span>:</span><span> repositoryWithAxios</span><span>(</span><span>'</span><span>posts</span><span>'</span><span>),
</span></span><span><span>    users</span><span>:</span><span> repositoryWithAxios</span><span>(</span><span>'</span><span>users</span><span>'</span><span>)
</span></span><span><span>    //...
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><h3><a><!--[-->Inject it<!--]--></a></h3><p><!--[-->Now the last (and mightiest) step has to be taken. Make the implementation available across our app so it can be used in components, </p><div><span></span><!--[-->asyncData<!--]--><!----></div> and so on. The good thing is, we don’t have to implement this on our own. It will be handled completely by the <div><span></span><!--[-->inject<!--]--><!----></div> method which is provided as the second argument to the plugin function:<!--]--><p></p><pre><!--[--><code><span><span>import</span><span> createRepository</span><span> from</span><span> '</span><span>~/api/repository.js</span><span>'
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> (</span><span>ctx</span><span>,</span><span> inject</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  const</span><span> repositoryWithAxios</span><span> =</span><span> createRepository</span><span>(</span><span>ctx</span><span>.</span><span>$axios</span><span>)
</span></span><span><span>
</span></span><span><span>  const</span><span> repositories</span><span> =</span><span> {
</span></span><span><span>    posts</span><span>:</span><span> repositoryWithAxios</span><span>(</span><span>'</span><span>posts</span><span>'</span><span>),
</span></span><span><span>    users</span><span>:</span><span> repositoryWithAxios</span><span>(</span><span>'</span><span>users</span><span>'</span><span>)
</span></span><span><span>    //...
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  inject</span><span>(</span><span>'</span><span>repositories</span><span>'</span><span>,</span><span> repositories</span><span>)
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[--></p><div><span></span><!--[-->inject<!--]--><!----></div> takes a name (used as key with <div><span></span><!--[-->$<!--]--><!----></div> prefix) as the first argument and the value of that key as the second argument, which can be a primitive, an object or a function.<!--]--><p></p><p><!--[-->Then </p><div><span></span><!--[-->inject<!--]--><!----></div> will:<!--]--><p></p><ul><!--[--><li><!--[-->Add the key-value pair to the Vue prototype (so <div><span></span><!--[-->this.$key<!--]--><!----></div> can be used in components and store<!--]--></li><li><!--[-->Add the key-value pair to the <div><span></span><!--[-->ctx.app<!--]--><!----></div> object (so it can be used in <div><span></span><!--[-->asyncData<!--]--><!----></div>, <div><span></span><!--[-->fetch<!--]--><!----></div> and so on)<!--]--></li><!--]--></ul><h2><a><!--[-->Use it<!--]--></a></h2><p><!--[-->We’ve completed our whole setup, so the only thing left is to actually use it. I’ve built a small <a><!--[--><!--[-->live demo with CodeSandbox<!--]--><!--]--></a> so you can directly play with it.<!--]--></p><p><!--[-->Make sure to check out the <a><!--[--><!--[-->Vue API query<!--]--><!--]--></a> plugin by Robson Tenório which provides an advanced query builder for your API. That package can definitely save you some work!<!--]--></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->Using an API repository to organize your calls is a mighty tool to keep track of your endpoints, call them uniformly, reduce duplicate code and also to have an easier time debugging or changing them!<!--]--></p><p><!--[-->The idea of injecting objects in the context and injecting other dependencies in a Nuxt plugin is a recurring pattern that can be used in a variety of scenarios. Keep that one in mind!<!--]--></p><p><!--[-->Still have questions? No problem, tweet me at <a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io).<!--]--></p><p><!--[-->Have you learned something by reading this post? Awesome, then I’ve reached my goal ? Also, please <strong><!--[-->spread the word<!--]--></strong> if you liked the article!<!--]--></p><style>html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sNpkn, html code.shiki .sNpkn{--shiki-default:#DBD7CAEE}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Guide To Write Universal SSR-ready Vue Components]]></title>
            <link>https://www.lichter.io/articles/universal-ssr-vue-component-guide/</link>
            <guid>https://www.lichter.io/articles/universal-ssr-vue-component-guide/</guid>
            <pubDate>Sat, 20 Oct 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[As a Vue developer, you may have heard the term server-side rendering. Even if you are not using a framework like Nuxt.js it is important to know how to write universal SSR-ready components. And guess what, it isn't even hard!]]></description>
            <content:encoded><![CDATA[<p><!--[-->As a Vue developer, you may have heard the term server-side rendering (SSR). Even if you are not using a framework like Nuxt.js or an SSR-plugin, it is important to know how to write <em><!--[-->universal components<!--]--></em>, or to put it in simpler words, components that can be interpreted from the server and the client.<!--]--></p><p><!--[-->If you ever want to switch to an SSR-based approach or share your component with people who do, this knowledge will definitely make your life easier! As a library/plugin author, this is a must in my opinion. And guess what, it isn’t even hard!<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Common pitfalls<!--]--></a><ul><!--[--><li><a><!--[-->window, document, and friends - platform-specific APIs<!--]--></a><!----></li><li><a><!--[-->Lifecycle hooks and side effects<!--]--></a><!----></li><li><a><!--[-->No data reactivity<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Directives<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Common pitfalls<!--]--></a></h2><p><!--[-->There are three very common and very problematic caveats that developers should think about when writing universal components. We’ll go through all of them one by one, showing examples of wrong and right implementations!<!--]--></p><p><!--[-->We will use <a><!--[--><!--[-->this CodeSandBox<!--]--><!--]--></a> for the examples.<!--]--></p><h3><a><!--[-->window, document, and friends - platform-specific APIs<!--]--></a></h3><p><!--[-->When the component is processed on the server-side, no dynamic updates will occur. That’s why only two lifecycle hooks will be executed on the server: </p><div><span></span><!--[-->beforeCreate<!--]--><!----></div> and <div><span></span><!--[-->created<!--]--><!----></div>. This means also, that those two hooks will be <strong><!--[-->called twice<!--]--></strong>. Once on the server and once on the client side.`<!--]--><p></p><p><!--[-->But on the server-side, there is no </p><div><span></span><!--[-->window<!--]--><!----></div> and no <div><span></span><!--[-->document<!--]--><!----></div> available (and no other browser-specific API like <div><span></span><!--[-->fetch<!--]--><!----></div>. If you try to call them in these two hooks, an error will be thrown on the server. The component <strong><!--[-->cannot be server-side-rendered<!--]--></strong> anymore! That behavior can also be observed in our <a><!--[--><!--[-->example for wrong usage<!--]--><!--]--></a>. SSR-users could still use the component wrapped in a <div><span></span><!--[-->&lt;ClientOnly&gt;&lt;/ClientOnly&gt;<!--]--><!----></div> tag but then it won’t have any impact on SEO and take longer to be visible at all.<!--]--><p></p><p><!--[-->This is by far the most common problem with “normal” components or 3rd party libraries in a server-side-rendered environment.<!--]--></p><p><!--[-->Rule of thumb: <strong><!--[-->Don’t call browser-specific APIs in </strong></p><div><strong><span></span><!--[-->created<!--]--><!----></strong></div><strong> or <div><span></span><!--[-->beforeCreate<!--]--><!----></div><!--]--></strong>. If you have to do this, then at least perform an availability check:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  created</span><span> ()</span><span> {
</span></span><span><span>    if</span><span> (</span><span>typeof</span><span> window</span><span> !==</span><span> '</span><span>undefined</span><span>'</span><span>)</span><span> {
</span></span><span><span>        window</span><span>.</span><span>scroll</span><span>(</span><span>/*...*/</span><span>)
</span></span><span><span>    }
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->But in most situations, it is completely fine to call them in </p><div><span></span><!--[-->beforeMount<!--]--><!----></div> or <div><span></span><!--[-->mounted<!--]--><!----></div>. If you have to use an API on the server and the client, make sure to have to available on both sides (for example use <div><span></span><!--[-->isomorphic-fetch<!--]--><!----></div> or <div><span></span><!--[-->axios<!--]--><!----></div>) if you want to send AJAX requests.<!--]--><p></p><p><!--[-->Also, you can sometimes leverage </p><div><span></span><!--[-->this.$el<!--]--><!----></div> inside a component (where <div><span></span><!--[-->$el<!--]--><!----></div> is the DOM element of the component itself). This can come in handy when binding event listeners or doing query selections.<!--]--><p></p><p><!--[-->An example component of correct usage can be found under </p><div><span></span><!--[-->components/platform/right.vue<!--]--><!----></div> in the CodeSandbox. The “bad example” is the <div><span></span><!--[-->wrong.vue<!--]--><!----></div> component in the same folder.<!--]--><p></p><h3><a><!--[-->Lifecycle hooks and side effects<!--]--></a></h3><p><!--[-->Speaking of lifecycle hooks! There is another thing you should think about: </p><div><span></span><!--[-->side effects<!--]--><!----></div>. A function or expression has a <div><span></span><!--[-->side effect<!--]--><!----></div> when it touches or modifies some state outside of the local environment. Examples are API calls, I/O operations, setting timers, using randomness, adding listeners, and so on.<!--]--><p></p><p><!--[-->To avoid memory leaks, you do not want to have side-effects in your </p><div><span></span><!--[-->created<!--]--><!----></div> and <div><span></span><!--[-->beforeCreate<!--]--><!----></div> hooks and you already know why! As these hooks getting called from the server-side as well, you cannot close a connection there or clear an interval. Instead, these objects will stay around forever and add up, causing a memory leak!<!--]--><p></p><p><!--[-->Rule of thumb: <strong><!--[-->Don’t use code with </strong></p><div><strong><span></span><!--[-->side-effects<!--]--><!----></strong></div><strong> in <div><span></span><!--[-->created<!--]--><!----></div> or <div><span></span><!--[-->beforeCreate<!--]--><!----></div><!--]--></strong>.<!--]--><p></p><p><!--[-->The examples can be found under </p><div><span></span><!--[-->components/side-effects.vue<!--]--><!----></div> in the CodeSandbox. Every time the <div><span></span><!--[-->Wrong.vue<!--]--><!----></div> component will be rendered on the server-side, a new interval will stay in the memory forever.<!--]--><p></p><p><!--[-->But <em><!--[-->when will it be server-side rendered<!--]--></em>? The answer is: every time a user enters the app over the </p><div><span></span><!--[-->side-effects/wrong.vue<!--]--><!----></div> <em><!--[-->page<!--]--></em> (under <div><span></span><!--[-->pages<!--]--><!----></div>).<!--]--><p></p><p><!--[-->Because the first page-request to the app will be server-side-rendered and all subsequent requests (through page navigation, on-page redirects and so on) will be rendered by the client. Page refreshes and other external redirects to the app count as “entry points” and will be server-side rendered.<!--]--></p><h3><a><!--[-->No data reactivity<!--]--></a></h3><p><!--[-->This is usually no big problem but it’s not bad to mention it anyway. There is no reactivity between the values on the server-side and on the client side.<!--]--></p><p><!--[-->If you manipulate your </p><div><span></span><!--[-->data<!--]--><!----></div> on the server-side, these changes will not be taken into account on the client side at all. If you jump into the <a><!--[--><!--[-->bad example<!--]--><!--]--></a>, you’ll see that the value quickly changes from <div><span></span><!--[-->10<!--]--><!----></div> to <div><span></span><!--[-->0<!--]--><!----></div> fast. In the code, I set zero as the default value in the data function and 10 on the server-side created method.<!--]--><p></p><h2><a><!--[-->Directives<!--]--></a></h2><p><!--[-->Custom Vue directives are often used to manipulate the DOM (eg. revealing elements on scroll or make them stick to a specific position). This won’t work on the server-side as we now know. But what can be done there?<!--]--></p><p><!--[-->Well, the easiest way is: <strong><!--[-->Don’t use directives as the abstraction, use components<!--]--></strong>. I did this with components like <a><!--[--><!--[-->VueNextLevelScroll<!--]--><!--]--></a> or <a><!--[--><!--[-->vue-if-bot<!--]--><!--]--></a> because it is way easier to make them universally usable and they can be code-split as well! You <strong><!--[-->don’t lose<!--]--></strong> anything by choosing components as an abstraction.<!--]--></p><p><!--[-->If you really want to use directives, you can add a server-side equivalent of this directive. In Nuxt, this is possible by setting up a </p><div><span></span><!--[-->directives<!--]--><!----></div> object in the <div><span></span><!--[-->this.options.render.bundleRenderer<!--]--><!----></div> object in the <div><span></span><!--[-->nuxt.config.js<!--]--><!----></div>. A good (but complex) example is the official <div><span></span><!--[-->v-model<!--]--><!----></div> <a><!--[--><!--[-->ssr directive<!--]--><!--]--></a>.<!--]--><p></p><p><!--[--><del><strong><!--[-->Attention<!--]--></strong>: Be aware to pass in your directives in </del></p><div><span></span><!--[-->kebab-case<!--]--><!----></div> (<div><span></span><!--[-->make-red<!--]--><!----></div> instead of <div><span></span><!--[-->makeRed<!--]--><!----></div>). Otherwise, they won’t be recognized! This is a bug in <div><span></span><!--[-->vue-server-renderer<!--]--><!----></div> (follow <a><!--[--><!--[-->this issue<!--]--><!--]--></a> for more info). This has been fixed in Vue 2.5.19!<!--]--><p></p><p><!--[-->The <a><!--[--><!--[-->matching example<!--]--><!--]--></a> will show you why you definitely should use components or server-side directives. Do you notice the flickering for the client-side-only directive example? It will be noticeable for all users that use this page as an entry point.<!--]--></p><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->Alright, let’s wrap it up, folks! If you want to go through the example code line by line, please take a look into <a><!--[--><!--[-->the CodeSandBox<!--]--><!--]--></a>.<!--]--></p><ul><!--[--><li><!--[-->Be careful when using <em><!--[-->platform-specific<!--]--></em> APIs, especially <div><span></span><!--[-->window<!--]--><!----></div> and <div><span></span><!--[-->document<!--]--><!----></div><!--]--></li><li><!--[-->Keep in mind that <div><span></span><!--[-->created<!--]--><!----></div> and <div><span></span><!--[-->beforeCreate<!--]--><!----></div> are executed on the server and client side. <strong><!--[-->No side effects, no <div><span></span><!--[-->window<!--]--><!----></div><!--]--></strong><!--]--></li><li><!--[-->There is no reactivity on the server side<!--]--></li><li><!--[-->Using directives isn’t always the best abstraction. But if you do use them, provide a <strong><!--[-->server-side directive<!--]--></strong><!--]--></li><!--]--></ul><p><!--[-->If you want further reading, I suggest reading the official <a><!--[--><!--[-->vue-ssr-docs<!--]--><!--]--></a>!<!--]--></p><p><!--[-->Still have questions? No problem, tweet me at <a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>, reach out on the Vue/Nuxt Discord or write me a mail (blog at lichter dot io)<!--]--></p><p><!--[-->I hope you enjoyed that article and are now a bit more aware of server-side-rendering compatibilities!
If this is the case, I’d gladly ask you to <strong><!--[-->spread the word<!--]--></strong>!<!--]--></p><style>html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Nuxt 2 on Brotli]]></title>
            <link>https://www.lichter.io/articles/nuxt-on-brotli/</link>
            <guid>https://www.lichter.io/articles/nuxt-on-brotli/</guid>
            <pubDate>Sun, 09 Sep 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Brotli, a somewhat new compression from Google is getting more and more traction. To minify your Nuxt.js application as good as possible, you should set up Brotli compression for it as well! Learn how to in this article.]]></description>
            <content:encoded><![CDATA[<p><!--[-->As a developer, you likely want to squeeze every unnecessary bit out of your Nuxt.js app. To accomplish this there are lots of nifty tools: From code-splitting over caching to compression. This post focuses on the latter.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Introduction<!--]--></a><!----></li><li><a><!--[-->Add Brotli to Nuxt.js<!--]--></a><!----></li><li><a><!--[-->Before we continue<!--]--></a><ul><!--[--><li><a><!--[-->Compression middleware<!--]--></a><!----></li><li><a><!--[-->Swap out the middleware<!--]--></a><!----></li><li><a><!--[-->Insert the middleware<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Compatibility<!--]--></a><!----></li><li><a><!--[-->Troubleshooting<!--]--></a><ul><!--[--><li><a><!--[-->Proxies<!--]--></a><!----></li><li><a><!--[-->Final Testing<!--]--></a><!----></li><li><a><!--[-->Brotli encoded XHR requests<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Closing remarks<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Introduction<!--]--></a></h2><p><!--[-->If you’ve ever heard compression related to the web, </p><div><span></span><!--[-->GZIP<!--]--><!----></div> was likely mentioned in the same sentence. It’s the most popular compression method and support by the browser for more than <em><!--[-->eighteen years<!--]--></em>!<!--]--><p></p><p><!--[-->Other compression methods were introduced in that time but for long none was able to show up better compression results, browser support, and compression speed.<!--]--></p><p><!--[-->This changed when <a><!--[--><!--[--></a></p><div><a><span></span><!--[-->Brotli<!--]--><!----></a></div><!--]--><!--]-->, an open-source algorithm from Google was released back in 2015.<!--]--><p></p><p><!--[--></p><div><span></span><!--[-->Brotli<!--]--><!----></div> has, similar to <div><span></span><!--[-->GZIP<!--]--><!----></div>, different compression levels which influence results and speed. Comparing the default setups of the algorithms, <div><span></span><!--[-->Brotli<!--]--><!----></div> excels in <strong><!--[-->both<!--]--></strong>, speed and file size! There is a <a><!--[--><!--[-->great article<!--]--><!--]--></a> from Akamai covering more statistics and details.<!--]--><p></p><p><!--[-->So, why shouldn’t we use </p><div><span></span><!--[-->Brotli<!--]--><!----></div> instead of <div><span></span><!--[-->GZIP<!--]--><!----></div> for our Nuxt.js 2 app?<!--]--><p></p><h2><a><!--[-->Add Brotli to Nuxt.js<!--]--></a></h2><p><!--[-->Already earlier in 2018 I thought this might be a <a><!--[--><!--[-->good idea<!--]--><!--]--></a> and now we are here! Adding </p><div><span></span><!--[-->Brotli<!--]--><!----></div> compression (with <div><span></span><!--[-->GZIP<!--]--><!----></div> fallback) is real in Nuxt 2. I’m afraid that the guide will not work for <div><span></span><!--[-->nuxt@1.4.X<!--]--><!----></div>) or lower. Also, you need <div><span></span><!--[-->SSR<!--]--><!----></div>/<div><span></span><!--[-->universal<!--]--><!----></div> mode enabled. If you use Nuxt as static site generator or SPA you have to configure your underlying server.<!--]--><p></p><h2><a><!--[-->Before we continue<!--]--></a></h2><p><!--[--><strong><!--[-->Important notice:<!--]--></strong> In general, I highly recommend to configure your platform provider, e.g. Heroku or AWS, or web server, like Apache or NGINX, to handle Brotli and GZIP compression because it will be way more performant. If you can’t for any reason, then setting it up in Nuxt is a decent option.<!--]--></p><h3><a><!--[-->Compression middleware<!--]--></a></h3><p><!--[-->Every time Nuxt.js renders a page it will call a bunch of middleware (f.ex. the error middleware to handle possible server-side errors). An important middleware that will be called is the <em><!--[-->compression middleware<!--]--></em> which will, as the name hints, compress the response. By default, the <a><!--[--><!--[--></a></p><div><a><span></span><!--[-->compression<!--]--><!----></a></div><!--]--><!--]--> package will be used for it. And there our first problem occurs…<!--]--><p></p><p><!--[-->This package <strong><!--[-->does not support<!--]--></strong> </p><div><span></span><!--[-->Brotli<!--]--><!----></div> (see <a><!--[--><!--[-->this GitHub issue<!--]--><!--]--></a>).<!--]--><p></p><h3><a><!--[-->Swap out the middleware<!--]--></a></h3><p><!--[-->What if we could swap out the middleware for another… would this solve our problem? At least it would bring us one step further to the goal.<!--]--></p><p><!--[-->And as I said above, with Nuxt 2 it’s <a><!--[--><!--[-->possible<!--]--><!--]--></a>.<!--]--></p><p><!--[-->Now we have to find a suitable middleware!<!--]--></p><p><!--[-->During my research, I found a package called <a><!--[--><!--[-->shrink-ray<!--]--><!--]--></a> which is actually a fork of the </p><div><span></span><!--[-->compression<!--]--><!----></div> package and would cover everything we need (and even more!). Unfortunately, the package is abandoned but after looking through a few issues and forks, I found out that a pretty active fork exists and was published as <a><!--[--><!--[--><div><span></span><!--[-->shrink-ray-current<!--]--><!----></div><!--]--><!--]--></a>. Thanks to the maintainer <a><!--[--><!--[-->Alorel<!--]--><!--]--></a> for keeping the <div><span></span><!--[-->shrink-ray<!--]--><!----></div> middleware alive!<!--]--><p></p><h3><a><!--[-->Insert the middleware<!--]--></a></h3><p><!--[-->Alright, all prerequisites fulfilled. Let’s get into the code!<!--]--></p><p><!--[-->First, install the packages (</p><div><span></span><!--[-->npm i shrink-ray-current nuxt<!--]--><!----></div>, of course, you can use <div><span></span><!--[-->yarn<!--]--><!----></div> as well). This might take a little as <div><span></span><!--[-->brotli<!--]--><!----></div> will be compiled directly on your device. If you have troubles installing <div><span></span><!--[-->shrink-ray-current<!--]--><!----></div>, be sure to check the package page and fulfill all prerequisites.<!--]--><p></p><p><!--[-->Now edit your </p><div><span></span><!--[-->nuxt.config.js<!--]--><!----></div> (or create one if there is none in your project yet) and change the <div><span></span><!--[-->render.compressor<!--]--><!----></div> parameter as described in the <a><!--[--><!--[-->docs<!--]--><!--]--></a> (the changes won’t be published to the <a><!--[--><!--[-->doc-website<!--]--><!--]--></a> until Nuxt 2 is out)<!--]--><p></p><p><!--[--></p><div><span></span><!--[-->nuxt.config.js<!--]--><!----></div><!--]--><p></p><pre><!--[--><code><span><span>import</span><span> shrinkRay</span><span> from</span><span> '</span><span>shrink-ray-current</span><span>'
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> {
</span></span><span><span>  render</span><span>:</span><span> {
</span></span><span><span>    compressor</span><span>:</span><span> shrinkRay</span><span>()
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Be careful to actually <strong><!--[-->invoke </strong></p><div><strong><span></span><!--[-->shrinkRay<!--]--><!----></strong></div><!--]--> (so don’t miss the <div><span></span><!--[-->()<!--]--><!----></div> after).<!--]--><p></p><p><!--[-->And that’s it! Start your app (in <em><!--[-->production mode<!--]--></em>, otherwise no compression will take place) and open your browser. Jump into your developer tools, select your network tab and reload the page. Now click the table header in the network tab and add the </p><div><span></span><!--[-->Content-Encoding<!--]--><!----></div> header (can be found under <em><!--[-->Response Headers<!--]--></em>) Then you should see that your requests are using <div><span></span><!--[-->Brotli<!--]--><!----></div>, denoted by a little <div><span></span><!--[-->br<!--]--><!----></div>.<!--]--><p></p><p><!--[-->If you see </p><div><span></span><!--[-->gzip<!--]--><!----></div> instead, then you should try to open the same page in Google Chrome. I’m a heavy Firefox user, but Firefox refused to enable <div><span></span><!--[-->Brotli<!--]--><!----></div> on localhost. As <div><span></span><!--[-->Brotli<!--]--><!----></div> will only be <strong><!--[-->served over <div><span></span><!--[-->HTTPS<!--]--><!----></div><!--]--></strong>, that seems like a correct behavior first but because <div><span></span><!--[-->localhost<!--]--><!----></div> is considered a “safe origin” (service workers are allowed, …) it’s a bug.<!--]--><p></p><p><!--[--><img><!--]--></p><p><!--[-->Firefox network tab displaying different scripts loaded with Brotli compression<!--]--></p><h2><a><!--[-->Compatibility<!--]--></a></h2><p><!--[-->Oh right, before we forget it! What happens if a user with an older browser (say IE 11) wants to connect to our app? Will they get an error?<!--]--></p><p><!--[--><strong><!--[-->No! That’s not a problem.<!--]--></strong> Our </p><div><span></span><!--[-->shrinkRay<!--]--><!----></div> middleware will check the <div><span></span><!--[-->Accept-Encoding<!--]--><!----></div> header of the request. It has been set by the client (f.ex. your browser) and provides information about the client’s supported compression formats. Depending on the header, <div><span></span><!--[-->shrinkRay<!--]--><!----></div> will apply <div><span></span><!--[-->Brotli<!--]--><!----></div>, <div><span></span><!--[-->GZIP<!--]--><!----></div> or even nothing at all!<!--]--><p></p><h2><a><!--[-->Troubleshooting<!--]--></a></h2><h3><a><!--[-->Proxies<!--]--></a></h3><p><!--[-->If you are using a proxy (likely the <a><!--[--><!--[--></a></p><div><a><span></span><!--[-->proxy-module<!--]--><!----></a></div><!--]--><!--]--> you <strong><!--[-->must exclude<!--]--></strong> these routes from your <div><span></span><!--[-->shrinkRay<!--]--><!----></div> middleware because weird serve errors will occur otherwise (I’ve spent some time to find out why the blog always threw server errors), for example JSON that is compressed (and therefor not processable). If this doesn’t help, try to change your API compression to <div><span></span><!--[-->GZIP<!--]--><!----></div> (not <div><span></span><!--[-->Brotli<!--]--><!----></div>, see below why) or to no compression at all.<!--]--><p></p><p><!--[-->I usually create a mapping from </p><div><span></span><!--[-->/api/<!--]--><!----></div> to <div><span></span><!--[-->api.myurl.com<!--]--><!----></div><!--]--><p></p><p><!--[--></p><div><span></span><!--[-->nuxt.config.js<!--]--><!----></div>:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  modules</span><span>:</span><span> [</span><span>'</span><span>@nuxtjs/axios</span><span>'</span><span>],
</span></span><span><span>  render</span><span>:</span><span> {
</span></span><span><span>    compressor</span><span>:</span><span> shrinkRay</span><span>()
</span></span><span><span>  },
</span></span><span><span>  // ...
</span></span><span><span>  proxy</span><span>:</span><span> {
</span></span><span><span>    '</span><span>/api/</span><span>'</span><span>:</span><span> {</span><span> target</span><span>:</span><span> '</span><span>api.myurl.com</span><span>'</span><span>,</span><span> pathRewrite</span><span>:</span><span> {</span><span> '</span><span>^/api/</span><span>'</span><span>:</span><span> ''</span><span> }</span><span> }
</span></span><span><span>  }
</span></span><span><span>  // ...
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->You now have to exclude all requests starting with </p><div><span></span><!--[-->/api/<!--]--><!----></div>. We leverage <div><span></span><!--[-->shrink-ray<!--]--><!----></div>'s <div><span></span><!--[-->filter<!--]--><!----></div> object and the built-in equally named function to implement this behavior.<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  modules</span><span>:</span><span> [</span><span>'</span><span>@nuxtjs/axios</span><span>'</span><span>],
</span></span><span><span>    render</span><span>:</span><span> {
</span></span><span><span>    compressor</span><span>:</span><span> shrinkRay</span><span>({
</span></span><span><span>      filter</span><span>:</span><span> (</span><span>req</span><span>,</span><span> res</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>        if</span><span> (</span><span>/</span><span>^</span><span>\/</span><span>api</span><span>/</span><span>.</span><span>test</span><span>(</span><span>req</span><span>.</span><span>originalUrl</span><span>))</span><span> {
</span></span><span><span>          return</span><span> false
</span></span><span><span>        }
</span></span><span><span>        return</span><span> shrinkRay</span><span>.</span><span>filter</span><span>(</span><span>req</span><span>,</span><span> res</span><span>)
</span></span><span><span>      }
</span></span><span><span>    })
</span></span><span><span>  },
</span></span><span><span>  // ...
</span></span><span><span>  proxy</span><span>:</span><span> {
</span></span><span><span>    '</span><span>/api/</span><span>'</span><span>:</span><span> {</span><span> target</span><span>:</span><span> '</span><span>api.myurl.com</span><span>'</span><span>,</span><span> pathRewrite</span><span>:</span><span> {</span><span> '</span><span>^/api/</span><span>'</span><span>:</span><span> ''</span><span> }</span><span> }
</span></span><span><span>  }
</span></span><span><span>  // ...
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Now we exclude all </p><div><span></span><!--[-->/api<!--]--><!----></div> requests and will otherwise delegate the filtering back to <div><span></span><!--[-->shrink-ray<!--]--><!----></div>.<!--]--><p></p><h3><a><!--[-->Final Testing<!--]--></a></h3><p><!--[-->To ensure that Brotli is enabled on your site now, you can use an online tool called <a><!--[--><!--[-->Brotli.Pro<!--]--><!--]--></a> which will tell you whether your website supports Brotli compression or not!<!--]--></p><h3><a><!--[-->Brotli encoded XHR requests<!--]--></a></h3><p><!--[--><strong><!--[-->UPDATE: Outdated! </strong></p><div><strong><span></span><!--[-->axios-module<!--]--><!----></strong></div><strong> v5.3.6 and later will ignore Brotli encoding on server side by default<!--]--></strong><!--]--><p></p><p><!--[--><del>Now you might think the next would be setting up </del></p><div><span></span><!--[-->Brotli<!--]--><!----></div> for your API as well, right? I <strong><!--[-->would not recommend<!--]--></strong> this as long as <div><span></span><!--[-->Brotli<!--]--><!----></div> has no native support from Node.js (see <a><!--[--><!--[-->this PR<!--]--><!--]--></a>). <div><span></span><!--[-->Axios<!--]--><!----></div> has no support for <div><span></span><!--[-->Brotli<!--]--><!----></div> on server-side by default and trying to monkey patch it isn’t worth it in my opinion. It is also not possible to change the <div><span></span><!--[-->Accept-Encoding<!--]--><!----></div> header through <div><span></span><!--[-->axios<!--]--><!----></div> (server-side). That leaves you with decompressing the <div><span></span><!--[-->Brotli<!--]--><!----></div> requests on your own, which, again, will get messy very fast.<!--]--><p></p><p><!--[--><del>On the client-side, axios will delegate the decoding to the browser (and most modern browser support </del></p><div><span></span><!--[-->Brotli<!--]--><!----></div> as we know)<!--]--><p></p><h2><a><!--[-->Closing remarks<!--]--></a></h2><p><!--[-->Once again, <strong><!--[-->we did it!<!--]--></strong> As a personal summary, I’ve decreased my blog’s index page size from 210kB to 170kB, which is a total of <strong><!--[-->19% decrease<!--]--></strong> in size.<!--]--></p><p><!--[-->As soon as you finished your setup (which should be soon because you reached the end of the post) be sure to tell me how many kilobytes you saved!<!--]--></p><p><!--[-->I’ve uploaded a sample setup for you on <a><!--[--><!--[-->GitHub<!--]--><!--]--></a>. All you have to do is to clone the repository, install the package and you are good to go!<!--]--></p><p><!--[-->All in all, I hope this article helped you a little. If so it would be awesome if you could <strong><!--[-->spread the word<!--]--></strong> (for example by using the buttons below the article).<!--]--></p><p><!--[--><strong><!--[-->Questions left? Critics? Have you successfully stepped through the setup?<!--]--></strong><!--]--></p><p><!--[-->Hit me up on Twitter (<a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>) or write me a mail (blog at lichter dot io).<!--]--></p><style>html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sBWcb, html code.shiki .sBWcb{--shiki-default:#E6CC77}html pre.shiki code .sWcty, html code.shiki .sWcty{--shiki-default:#C4704F}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sending emails through Nuxt 2]]></title>
            <link>https://www.lichter.io/articles/nuxt-emails/</link>
            <guid>https://www.lichter.io/articles/nuxt-emails/</guid>
            <pubDate>Tue, 14 Aug 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Who can't relate to this: You've built a small portfolio page for someone (maybe a company, a friend or yourself) and the only API endpoint you'd need is one for a form. What now? In this article, I'll explain how to actually send emails and process forms...]]></description>
            <content:encoded><![CDATA[<p><!--[-->Who can’t relate to this: You’ve built a small portfolio page for someone, maybe a company, a friend or yourself. And the only API endpoint you’d need is one for a form. What now? Scaffolding a new service just for this one endpoint?<!--]--></p><p><!--[-->Fear no more! It’s possible to send emails almost directly through Nuxt but only in SSR mode with a Node.js server running. No more additional API server necessary if you want to send a mail with data coming from a contact form.<!--]--></p><p><!--[-->Before diving into the implementation and ideas behind it, here is the <a><!--[--><!--[-->full source code<!--]--><!--]--></a> I’m referring to through this blog post. It’s from my company’s website <a><!--[--><!--[-->developmint.de<!--]--><!--]--></a>.<!--]--></p><p><!--[-->The main goal was a simple API endpoint accessible through Nuxt.js to handle the three contact form fields (<em><!--[-->name<!--]--></em>, <em><!--[-->email<!--]--></em> and <em><!--[-->message<!--]--></em>) and to send an email with the data if everything is alright.<!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Let’s get it on - serverMiddleware<!--]--></a><!----></li><li><a><!--[-->Express in Nuxt.js?<!--]--></a><!----></li><li><a><!--[-->Still missing: the mailer<!--]--></a><ul><!--[--><li><a><!--[-->Inserting the body-parser<!--]--></a><!----></li><li><a><!--[-->Validate and sanitize<!--]--></a><!----></li><li><a><!--[-->Send it out<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Finally we can send emails - A conclusion<!--]--></a><!----></li><li><a><!--[-->Closing remarks<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Let’s get it on - serverMiddleware<!--]--></a></h2><p><!--[-->While developing the <a><!--[--><!--[-->redirect-module<!--]--><!--]--></a> for Nuxt 2, I came in touch with <a><!--[--><!--[-->serverMiddleware<!--]--><!--]--></a>. Those run before the actual </p><div><span></span><!--[-->vue-server-renderer<!--]--><!----></div> and are usually used for handling static assets, forcing HTTPS or, in case of <a><!--[--><!--[--><div><span></span><!--[-->redirect-module<!--]--><!----></div><!--]--><!--]--></a>, rewriting routes.<!--]--><p></p><p><!--[-->But as they are highly customizable and flexible, why not use them as endpoints instead?<!--]--></p><p><!--[-->This is <strong><!--[-->possible<!--]--></strong>. An example middleware could look like this:<!--]--></p><pre><!--[--><code><span><span>export</span><span> default</span><span> (</span><span>req</span><span>,</span><span> res</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  res</span><span>.</span><span>write</span><span>(</span><span>'</span><span>Hey!</span><span>'</span><span>)
</span></span><span><span>  res</span><span>.</span><span>end</span><span>()
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->When saved to </p><div><span></span><!--[-->api/test.js<!--]--><!----></div> for example, you can then add it to your Nuxt config:<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  // ...
</span></span><span><span>  serverMiddleware</span><span>:</span><span> [
</span></span><span><span>    {</span><span> path</span><span>:</span><span> '</span><span>/api/test</span><span>'</span><span>,</span><span> handler</span><span>:</span><span> '</span><span>~/api/test</span><span>'</span><span> },
</span></span><span><span>  ],
</span></span><span><span>  // ...
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->If you rebuild your project in dev mode (</p><div><span></span><!--[-->yarn run dev<!--]--><!----></div>) and visit <div><span></span><!--[-->/api/test<!--]--><!----></div>, you can see <div><span></span><!--[-->Hey!<!--]--><!----></div> as the page content. <strong><!--[-->Great!<!--]--></strong><!--]--><p></p><p><!--[-->We can use server middleware to serve content… But there must be a drawback, right?<!--]--></p><p><!--[--><strong><!--[-->Right<!--]--></strong> one could think…<!--]--></p><p><!--[-->As Nuxt uses <a><!--[--><!--[-->connect<!--]--><!--]--></a> as middleware layer (to reduce overhead as it suffices the complexity needed), we are missing some “critical” features in comparison to <a><!--[--><!--[-->express<!--]--><!--]--></a>.<!--]--></p><p><!--[-->Besides typical convenience features and routing (which isn’t even mandatory in our case), we can’t get the passed parameters from our </p><div><span></span><!--[-->req<!--]--><!----></div> object at the moment. Without those, there is no content for our contact form mail. So what now?<!--]--><p></p><p><!--[-->We could use the <a><!--[--><!--[-->body-parser<!--]--><!--]--></a> package and apply it to the route before we use our custom middleware but then we’d face more “problems” like decoding JSON or setting headers “correctly” sooner or later. Likely it would work from a certain point on but there must be a better way. If we could just use <em><!--[-->express<!--]--></em>…<!--]--></p><h2><a><!--[-->Express in Nuxt.js?<!--]--></a></h2><p><!--[-->Possibly you have heard it the other way: An express app with Nuxt.js as renderer (like in <a><!--[--><!--[-->express-template<!--]--><!--]--></a>).<!--]--></p><p><!--[-->But <strong><!--[-->did you know<!--]--></strong> that you can use </p><div><span></span><!--[-->express<!--]--><!----></div> inside a <div><span></span><!--[-->serverMiddleware<!--]--><!----></div>?<!--]--><p></p><pre><!--[--><code><span><span>import</span><span> express</span><span> from</span><span> '</span><span>express</span><span>'
</span></span><span><span>const</span><span> app</span><span> =</span><span> express</span><span>()
</span></span><span><span>
</span></span><span><span>app</span><span>.</span><span>post</span><span>(</span><span>'</span><span>/</span><span>'</span><span>,</span><span> (</span><span>req</span><span>,</span><span> res</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>    // Validate, sanitize and send
</span></span><span><span>})
</span></span><span><span>
</span></span><span><span>export</span><span> default</span><span> {
</span></span><span><span>  path</span><span>:</span><span> '</span><span>/api/contact</span><span>'</span><span>,
</span></span><span><span>  handler</span><span>:</span><span> app
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->We declare the express app as the middleware handler and Nuxt is magically gluing everything together.<!--]--></p><p><!--[-->Now we can save this short snippet under </p><div><span></span><!--[-->api/contact.js<!--]--><!----></div> and register our custom server middleware only as path string (because path and handler are inside).<!--]--><p></p><pre><!--[--><code><span><span>export</span><span> default</span><span> {
</span></span><span><span>  // ...
</span></span><span><span>  serverMiddleware</span><span>:</span><span> [
</span></span><span><span>    '</span><span>~/api/contact</span><span>'
</span></span><span><span>  ],
</span></span><span><span>  // ...
</span></span><span><span>}
</span></span></code><!--]--></pre><h2><a><!--[-->Still missing: the mailer<!--]--></a></h2><p><!--[-->The last coding part might be less spectacular for everybody who already set up <a><!--[--><!--[-->nodemailer<!--]--><!--]--></a> in an express app.<!--]--></p><p><!--[--><em><!--[-->Fun fact<!--]--></em>: Before this implementation I did not as I mostly write backends in Laravel (♥️).<!--]--></p><h3><a><!--[-->Inserting the body-parser<!--]--></a></h3><p><!--[-->Since version 4.16.0, express has its own JSON middleware based on body-parser. To get our JSON parameters out of the POST body, we will need it:<!--]--></p><pre><!--[--><code><span><span>import</span><span> express</span><span> from</span><span> '</span><span>express</span><span>'
</span></span><span><span>import</span><span> nodemailer</span><span> from</span><span> '</span><span>nodemailer</span><span>'
</span></span><span><span>
</span></span><span><span>const</span><span> app</span><span> =</span><span> express</span><span>()
</span></span><span><span>
</span></span><span><span>app</span><span>.</span><span>use</span><span>(</span><span>express</span><span>.</span><span>json</span><span>())
</span></span><span><span>// ...
</span></span></code><!--]--></pre><h3><a><!--[-->Validate and sanitize<!--]--></a></h3><p><!--[-->Now we can get back to our </p><div><span></span><!--[-->post<!--]--><!----></div> route. You may wonder why it’s declared as <div><span></span><!--[-->/<!--]--><!----></div> instead of <div><span></span><!--[-->/api/contact<!--]--><!----></div>. That’s because our <div><span></span><!--[-->express<!--]--><!----></div> app’s base route is <div><span></span><!--[-->/api/contact<!--]--><!----></div> (set through the <div><span></span><!--[-->path<!--]--><!----></div> export).<!--]--><p></p><pre><!--[--><code><span><span>import</span><span> express</span><span> from</span><span> '</span><span>express</span><span>'
</span></span><span><span>import</span><span> validator</span><span> from</span><span> '</span><span>validator</span><span>'
</span></span><span><span>import</span><span> xssFilters</span><span> from</span><span> '</span><span>xss-filters</span><span>'
</span></span><span><span>
</span></span><span><span>const</span><span> app</span><span> =</span><span> express</span><span>()
</span></span><span><span>
</span></span><span><span>app</span><span>.</span><span>use</span><span>(</span><span>express</span><span>.</span><span>json</span><span>())
</span></span><span><span>
</span></span><span><span>app</span><span>.</span><span>post</span><span>(</span><span>'</span><span>/</span><span>'</span><span>,</span><span> (</span><span>req</span><span>,</span><span> res</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  const</span><span> attributes</span><span> =</span><span> [</span><span>'</span><span>name</span><span>'</span><span>,</span><span> '</span><span>email</span><span>'</span><span>,</span><span> '</span><span>msg</span><span>'</span><span>]</span><span> // Our three form fields, all required
</span></span><span><span>
</span></span><span><span>  // Map each attribute name to the validated and sanitized equivalent (false if validation failed)
</span></span><span><span>  const</span><span> sanitizedAttributes</span><span> =</span><span> attributes</span><span>.</span><span>map</span><span>(</span><span>n</span><span> =&gt;</span><span> validateAndSanitize</span><span>(</span><span>n</span><span>,</span><span> req</span><span>.</span><span>body</span><span>[</span><span>n</span><span>]))
</span></span><span><span>
</span></span><span><span>  // True if some of the attributes new values are false -&gt; validation failed
</span></span><span><span>  const</span><span> someInvalid</span><span> =</span><span> sanitizedAttributes</span><span>.</span><span>some</span><span>(</span><span>r</span><span> =&gt;</span><span> !</span><span>r</span><span>)
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>someInvalid</span><span>)</span><span> {
</span></span><span><span>    // Throw a 422 with a neat error message if validation failed
</span></span><span><span>    return</span><span> res</span><span>.</span><span>status</span><span>(</span><span>422</span><span>).</span><span>json</span><span>({</span><span> '</span><span>error</span><span>'</span><span>:</span><span> '</span><span>Ugh.. That looks unprocessable!</span><span>'</span><span> })
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  // Upcoming here: sending the mail
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->Let’s take a look at the </p><div><span></span><!--[-->validateAndSanitize<!--]--><!----></div> function. It could be replaced with another express middleware or plugin but why not writing our own this time:<!--]--><p></p><pre><!--[--><code><span><span>const</span><span> rejectFunctions</span><span> =</span><span> new</span><span> Map</span><span>([
</span></span><span><span>  [</span><span> '</span><span>name</span><span>'</span><span>,</span><span> v</span><span> =&gt;</span><span> v</span><span>.</span><span>length</span><span> &lt;</span><span> 4</span><span> ],
</span></span><span><span>  [</span><span> '</span><span>email</span><span>'</span><span>,</span><span> v</span><span> =&gt;</span><span> !</span><span>validator</span><span>.</span><span>isEmail</span><span>(</span><span>v</span><span>)</span><span> ],
</span></span><span><span>  [</span><span> '</span><span>msg</span><span>'</span><span>,</span><span> v</span><span> =&gt;</span><span> v</span><span>.</span><span>length</span><span> &lt;</span><span> 25</span><span> ]
</span></span><span><span>])
</span></span><span><span>const</span><span> validateAndSanitize</span><span> =</span><span> (</span><span>key</span><span>,</span><span> value</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  // If map has key and function returns false, return sanitized input. Else, return false
</span></span><span><span>  return</span><span> rejectFunctions</span><span>.</span><span>has</span><span>(</span><span>key</span><span>)</span><span> &amp;&amp;</span><span> !</span><span>rejectFunctions</span><span>.</span><span>get</span><span>(</span><span>key</span><span>)(</span><span>value</span><span>)</span><span> &amp;&amp;</span><span> xssFilters</span><span>.</span><span>inHTMLData</span><span>(</span><span>value</span><span>)
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Each possible attribute receives a </p><div><span></span><!--[-->rejectFunction<!--]--><!----></div> that defines in which case the validation will fail. If the function returns false, the validation passed. It looks weird first but I like the reversed approach here because we can avoid a cascade of <div><span></span><!--[-->if<!--]--><!----></div>s.<!--]--><p></p><h3><a><!--[-->Send it out<!--]--></a></h3><p><!--[-->After validating and sanitizing, we are confident that we can send the mail out!<!--]--></p><pre><!--[--><code><span><span>import</span><span> express</span><span> from</span><span> '</span><span>express</span><span>'
</span></span><span><span>import</span><span> nodemailer</span><span> from</span><span> '</span><span>nodemailer</span><span>'
</span></span><span><span>import</span><span> validator</span><span> from</span><span> '</span><span>validator</span><span>'
</span></span><span><span>import</span><span> xssFilters</span><span> from</span><span> '</span><span>xss-filters</span><span>'
</span></span><span><span>// ...
</span></span><span><span>
</span></span><span><span>app</span><span>.</span><span>post</span><span>(</span><span>'</span><span>/</span><span>'</span><span>,</span><span> (</span><span>req</span><span>,</span><span> res</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  // ...
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>someInvalid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> res</span><span>.</span><span>status</span><span>(</span><span>422</span><span>).</span><span>json</span><span>({</span><span> '</span><span>error</span><span>'</span><span>:</span><span> '</span><span>Ugh.. That looks unprocessable!</span><span>'</span><span> })
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  sendMail</span><span>(...</span><span>sanitizedAttributes</span><span>)
</span></span><span><span>  res</span><span>.</span><span>status</span><span>(</span><span>200</span><span>).</span><span>json</span><span>({</span><span> '</span><span>message</span><span>'</span><span>:</span><span> '</span><span>OH YEAH</span><span>'</span><span> })
</span></span><span><span>})
</span></span></code><!--]--></pre><p><!--[-->We use the ES6 spread syntax to pass the sanitized values to the </p><div><span></span><!--[-->sendMail<!--]--><!----></div> function:<!--]--><p></p><pre><!--[--><code><span><span>const</span><span> sendMail</span><span> =</span><span> (</span><span>name</span><span>,</span><span> email</span><span>,</span><span> msg</span><span>)</span><span> =&gt;</span><span> {
</span></span><span><span>  const</span><span> transporter</span><span> =</span><span> nodemailer</span><span>.</span><span>createTransport</span><span>({
</span></span><span><span>    sendmail</span><span>:</span><span> true</span><span>,
</span></span><span><span>    newline</span><span>:</span><span> '</span><span>unix</span><span>'</span><span>,
</span></span><span><span>    path</span><span>:</span><span> '</span><span>/usr/sbin/sendmail</span><span>'
</span></span><span><span>  })
</span></span><span><span>  transporter</span><span>.</span><span>sendMail</span><span>({
</span></span><span><span>    from</span><span>:</span><span> email</span><span>,
</span></span><span><span>    to</span><span>:</span><span> '</span><span>support@developmint.de</span><span>'</span><span>,
</span></span><span><span>    subject</span><span>:</span><span> '</span><span>New contact form message</span><span>'</span><span>,
</span></span><span><span>    text</span><span>:</span><span> msg
</span></span><span><span>  })
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Inside we create a nodemailer transporter and send the email out. We could do this through </p><div><span></span><!--[-->SMTP<!--]--><!----></div>, other providers (e.g. SES) or (classically) through <div><span></span><!--[-->sendmail<!--]--><!----></div> as I did. If you want to know more about the setup of nodemailer, <a><!--[--><!--[-->here you go<!--]--><!--]--></a>.<!--]--><p></p><h2><a><!--[-->Finally we can send emails - A conclusion<!--]--></a></h2><p><!--[-->So, we did it! If we now send a POST request (e.g. with </p><div><span></span><!--[-->axios<!--]--><!----></div>) through our form, the email will be sent.<!--]--><p></p><p><!--[--><strong><!--[-->Was it worth it?<!--]--></strong> - <strong><!--[-->Definitely!<!--]--></strong> Instead of blocking another port for such a simple API, we can run it together with our Nuxt server (in SSR mode).<!--]--></p><p><!--[--><strong><!--[-->Should I adapt my whole API to leverage Nuxt server middleware now?<!--]--></strong> - You could do this, but I would rather <strong><!--[-->not recommend it<!--]--></strong>, as pointed out in a <a><!--[--><!--[-->recent article<!--]--><!--]--></a>. It’s a great solution for simple and small APIs, but as soon as complexity or the request count increases, better go with own API servers (not only because of performance, also because of better scalability and no “single point of failure”).<!--]--></p><h2><a><!--[-->Closing remarks<!--]--></a></h2><p><!--[-->I hope you enjoyed the article! If so it’d be cool if you could spread the word ☺️<!--]--></p><p><!--[--><strong><!--[-->Questions left? Critics?<!--]--></strong> Hit me up on Twitter (<a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>) or write me a mail (blog at lichter dot io). I’m curious to hear from you!<!--]--></p><style>html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sxA9i, html code.shiki .sxA9i{--shiki-default:#4C9A91}</style>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Refactoring code - A case study (#1 - Response handling)]]></title>
            <link>https://www.lichter.io/articles/refactoring-case-study-response-handling/</link>
            <guid>https://www.lichter.io/articles/refactoring-case-study-response-handling/</guid>
            <pubDate>Tue, 14 Aug 2018 00:00:00 GMT</pubDate>
            <description><![CDATA[Recently I stumbled upon a very interesting code sample which I had to review. As I'm a huge clean code advocate, I'll dissect the small code piece with you and explain several techniques that help to write clean, human-readable and maintainable code.]]></description>
            <content:encoded><![CDATA[<p><!--[--><em><!--[-->Remark: The following advice and techniques are valid for many languages, not only Javascript! Also be aware that the upcoming information is opinionated.<!--]--></em><!--]--></p><p><!--[--><strong><!--[-->Update (19.08.2018):<!--]--></strong> Improved the code by using <em><!--[-->Map<!--]--></em><!--]--></p><div><!--[--><h2><a>Table of Contents</a></h2><ul><!--[--><li><a><!--[-->Introduction<!--]--></a><!----></li><li><a><!--[-->Initial code<!--]--></a><ul><!--[--><li><a><!--[-->Problems of the initial code<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Iterations<!--]--></a><ul><!--[--><li><a><!--[-->First iteration - Remove temporary variables<!--]--></a><!----></li><li><a><!--[-->Second iteration - Invert guarding if-statements<!--]--></a><!----></li><li><a><!--[-->Third iteration - Remove else<!--]--></a><!----></li><li><a><!--[-->Fourth iteration - Deal with duplications in the logic<!--]--></a><!----></li><li><a><!--[-->Fifth iteration - Lookup table<!--]--></a><!----></li><li><a><!--[-->Sixth iteration - Invalid state<!--]--></a><!----></li><!--]--></ul></li><li><a><!--[-->Wrapping it up<!--]--></a><!----></li><li><a><!--[-->Conclusion<!--]--></a><!----></li><!--]--></ul><!--]--></div><h2><a><!--[-->Introduction<!--]--></a></h2><p><!--[-->I held a code review on a clients project recently, as usual when the team has been implemented a new feature. The code was ‘working’, but far from optimal because a naive approach has been taken. This is totally fine, as reviews are for such situations. But after writing code I suggest to look over it once again and to refactor it <em><!--[-->on the fly<!--]--></em> (after the tests are passing).<!--]--></p><p><!--[-->Let me outline the context for the code:<!--]--></p><p><!--[-->Think about taking a course or participating in a seminar. After people have signed up for such a seminar, they receive an email with a <em><!--[-->confirmation link<!--]--></em>. After confirming that they are participating, they get a <em><!--[-->confirmation mail<!--]--></em> with a link to <em><!--[-->cancel<!--]--></em> the participation. Both links will call a website, which calls then an API with the submitted tokens. The response of this API call is the condition for the text that will be displayed on the website. Then our function comes in. <strong><!--[-->It will return the text shown depending on the API response<!--]--></strong>. Also, it is a <em><!--[-->pure function<!--]--></em> (no side-effects). Catching request errors has been done before.<!--]--></p><h2><a><!--[-->Initial code<!--]--></a></h2><p><!--[-->Now here goes the code. Try to <strong><!--[-->read over it just once<!--]--></strong>:<!--]--></p><pre><!--[--><code><span><span>const</span><span> evaluateResponseOld</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  let</span><span> text</span><span> =</span><span> ''
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Confirmation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>Thanks for your subscription</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }</span><span> else</span><span> if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Cancellation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>You have been unsubscribed successfully</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }
</span></span><span><span>  }</span><span> else</span><span> {
</span></span><span><span>    text</span><span> =</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>  return</span><span> text
</span></span><span><span>}
</span></span></code><!--]--></pre><h3><a><!--[-->Problems of the initial code<!--]--></a></h3><p><!--[-->Were you able to find out all the different cases and returns after one read? I <em><!--[-->wasn’t<!--]--></em> back then, though I read code every day.<!--]--></p><h4><a><!--[-->High cognitive load<!--]--></a></h4><p><!--[-->So, as you likely have recognized, the code is unnecessarily complex and has, therefore, a high cognitive load. You always have to remember which condition was fulfilled. Also, the </p><div><span></span><!--[-->text<!--]--><!----></div> variable is mutable, so you’d have to think about its state as well. In this case, it’ll only be set once, but imagine more complex functions.<!--]--><p></p><h4><a><!--[-->Difficult Maintainability<!--]--></a></h4><p><!--[-->Now imagine that an additional state must be handled because the API will respond to another scenario now (for example </p><div><span></span><!--[-->Cancellation<!--]--><!----></div>, <div><span></span><!--[-->Confirmation<!--]--><!----></div> and <div><span></span><!--[-->Pending<!--]--><!----></div>). It will take some time to add the state and furthermore you have to duplicate a lot of code (see <div><span></span><!--[-->response.used<!--]--><!----></div> in the current code). Which leads me to…<!--]--><p></p><h4><a><!--[-->Code duplication<!--]--></a></h4><p><!--[-->Code duplication per se is better than a wrong abstraction but in this case the code is unnecessarily duplicated and therefore more error-prone. Think about you have to change </p><div><span></span><!--[-->used<!--]--><!----></div> to <div><span></span><!--[-->invalid<!--]--><!----></div> now. If you miss one <div><span></span><!--[-->if<!--]--><!----></div> statement, the code won’t work. Fortunately, your tests will tell you (if you have such…).<!--]--><p></p><h2><a><!--[-->Iterations<!--]--></a></h2><p><!--[-->So… how can we do better? We will look into some techniques I use to transform this code in a more readable snippet. It only takes six small iterations!<!--]--></p><h3><a><!--[-->First iteration - Remove temporary variables<!--]--></a></h3><h4><a><!--[-->Motivation<!--]--></a></h4><p><!--[-->So, why should we remove temporary variables from the code (as far as possible)? Well, first of all, it reduces cognitive load. When almost all variables are immutable (or no variables exist), you don’t have to think about their state! In our scenario, we can even omit the </p><div><span></span><!--[-->text<!--]--><!----></div> variable completely as the function will <strong><!--[-->always return text<!--]--></strong> and no side-effects are included.<!--]--><p></p><h4><a><!--[-->Code<!--]--></a></h4><pre><!--[--><code><span><span>const</span><span> stepOne</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Confirmation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        return</span><span> '</span><span>Thanks for your subscription</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }</span><span> else</span><span> if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Cancellation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        return</span><span> '</span><span>You have been unsubscribed successfully</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }
</span></span><span><span>  }</span><span> else</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><h3><a><!--[-->Second iteration - Invert guarding if-statements<!--]--></a></h3><h4><a><!--[-->Motivation<!--]--></a></h4><p><!--[-->Guarding </p><div><span></span><!--[-->if<!--]--><!----></div>s are, as the name might hint <div><span></span><!--[-->if<!--]--><!----></div> conditions around a larger sub-block. They are <em><!--[-->guarding<!--]--></em> the code with their condition so that it will only execute when the <div><span></span><!--[-->if<!--]--><!----></div> evaluates to true. No bad thing in general, but nested guarding ifs lead to increased cognitive load and unwanted complexity. Instead, we can <strong><!--[-->invert the condition<!--]--></strong> and insert an <strong><!--[-->early return<!--]--></strong>.<!--]--><p></p><h4><a><!--[-->Code<!--]--></a></h4><pre><!--[--><code><span><span>const</span><span> stepTwo</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }</span><span> else</span><span> {
</span></span><span><span>    if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Confirmation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        return</span><span> '</span><span>Thanks for your subscription</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }</span><span> else</span><span> if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Cancellation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        return</span><span> '</span><span>You have been unsubscribed successfully</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><h3><a><!--[-->Third iteration - Remove else<!--]--></a></h3><h4><a><!--[-->Motivation<!--]--></a></h4><p><!--[-->Okay, this might be a bit controversial but I usually consider <strong><!--[--></strong></p><div><strong><span></span><!--[-->else<!--]--><!----></strong></div><strong> as a code smell<!--]--></strong>. If you use early returns and return in each nested block, they won’t give you any value. There are some situations though where <div><span></span><!--[-->else<!--]--><!----></div> is perfectly fine.<!--]--><p></p><p><!--[-->Normally I’d replace all </p><div><span></span><!--[-->else if<!--]--><!----></div>s with <div><span></span><!--[-->if<!--]--><!----></div>s as well but to make the next iteration more obvious we will keep them to signalize that the conditions are related to each other.<!--]--><p></p><h4><a><!--[-->Code<!--]--></a></h4><pre><!--[--><code><span><span>const</span><span> stepThree</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Confirmation</span><span>'</span><span>)</span><span> {
</span></span><span><span>    if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>      return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>    }
</span></span><span><span>    return</span><span> '</span><span>Thanks for your subscription</span><span>'
</span></span><span><span>  }</span><span> else</span><span> if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Cancellation</span><span>'</span><span>)</span><span> {
</span></span><span><span>    if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>      return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>    }
</span></span><span><span>    return</span><span> '</span><span>You have been unsubscribed successfully</span><span>'
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><h3><a><!--[-->Fourth iteration - Deal with duplications in the logic<!--]--></a></h3><h4><a><!--[-->Motivation<!--]--></a></h4><p><!--[-->Now we should focus on the “leftovers”. As the code is more readable we can take a look and find possible duplicates in the code branches.<!--]--></p><p><!--[-->If we look over our transformed code we now see clearly that the </p><div><span></span><!--[-->response.used<!--]--><!----></div> condition is <strong><!--[-->independent<!--]--></strong> of the <div><span></span><!--[-->response.type<!--]--><!----></div>. This means we can extract this part and move it <em><!--[-->one scope up<!--]--></em>, which makes the condition to another inverted guarding <div><span></span><!--[-->if<!--]--><!----></div>.<!--]--><p></p><h4><a><!--[-->Code<!--]--></a></h4><pre><!--[--><code><span><span>const</span><span> stepFour</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Confirmation</span><span>'</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>Thanks for your subscription</span><span>'
</span></span><span><span>  }</span><span> else</span><span> if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Cancellation</span><span>'</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>You have been unsubscribed successfully</span><span>'
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><h3><a><!--[-->Fifth iteration - Lookup table<!--]--></a></h3><h4><a><!--[-->Motivation<!--]--></a></h4><p><!--[-->One criterion was that we should be able to add new response states easily. To do this, we should minimize the code we would need for it. Currently, we’d have to add another </p><div><span></span><!--[-->if<!--]--><!----></div>, another comparison and a return statement. Doesn’t seem that much, but again, think about more complex functions.<!--]--><p></p><p><!--[-->To reduce the changes when adding a </p><div><span></span><!--[-->type<!--]--><!----></div> (f.ex. <em><!--[-->Pending<!--]--></em>) and the text that should be returned, we’ll transform our control flow from an <div><span></span><!--[-->if-else-if<!--]--><!----></div> structure into a lookup table. This works well if you have the <strong><!--[-->same comparison method again and again<!--]--></strong> (in our case triple equals) in your conditions.<!--]--><p></p><p><!--[-->We will create a </p><div><span></span><!--[-->lookup<!--]--><!----></div> object and fill it with the response types which will act as the keys, together with the corresponding text as a value so we can dynamically access the text based on the response type. To add a new type/state we only have to add what has <em><!--[-->really changed<!--]--></em>: The new type and the related text.<!--]--><p></p><h4><a><!--[-->Code<!--]--></a></h4><pre><!--[--><code><span><span>const</span><span> stepFive</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  const</span><span> responseLookup</span><span> =</span><span> {
</span></span><span><span>    Confirmation</span><span>:</span><span> '</span><span>Thanks for your subscription</span><span>'</span><span>,
</span></span><span><span>    Cancellation</span><span>:</span><span> '</span><span>You have been unsubscribed successfully</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>responseLookup</span><span>.</span><span>hasOwnProperty</span><span>(</span><span>response</span><span>.</span><span>type</span><span>))</span><span> {
</span></span><span><span>    return</span><span> responseLookup</span><span>[</span><span>response</span><span>.</span><span>type</span><span>]
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->You could also use a </p><div><span></span><!--[-->Map<!--]--><!----></div> to increase the lookup speed (thanks to <a><!--[--><!--[-->Adrien Baron<!--]--><!--]--></a> for the hint).<!--]--><p></p><pre><!--[--><code><span><span>const</span><span> stepFiveWithMap</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  const</span><span> responseMap</span><span> =</span><span> new</span><span> Map</span><span>([
</span></span><span><span>    [</span><span>'</span><span>Confirmation</span><span>'</span><span>,</span><span>'</span><span>Thanks for your subscription</span><span>'</span><span>],
</span></span><span><span>    [</span><span>'</span><span>Cancellation</span><span>'</span><span>,</span><span>'</span><span>You have been unsubscribed successfully</span><span>'</span><span>]
</span></span><span><span>  ])
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>responseMap</span><span>.</span><span>has</span><span>(</span><span>response</span><span>.</span><span>type</span><span>))</span><span> {
</span></span><span><span>    return</span><span> responseMap</span><span>.</span><span>get</span><span>(</span><span>response</span><span>.</span><span>type</span><span>)
</span></span><span><span>  }
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[-->Placing the lookup object outside would also be viable as long as the lookup object don’t change through the function. This makes your function <em><!--[-->impure<!--]--></em> though, because it’ll then depend on an outside object and not only on it’s inputs.<!--]--></p><p><!--[-->To solve this, you could take both parameters (the </p><div><span></span><!--[-->response<!--]--><!----></div> and the <div><span></span><!--[-->lookupObject<!--]--><!----></div>) or create a higher order function, taking a <div><span></span><!--[-->lookupMap<!--]--><!----></div> first, which will make your function easily reusable. A <strong><!--[-->higher order function<!--]--></strong> is a function that returns a function again. The implementation would look like this:<!--]--><p></p><pre><!--[--><code><span><span>const</span><span> responseMap</span><span> =</span><span> new</span><span> Map</span><span>([
</span></span><span><span>  [</span><span>'</span><span>Confirmation</span><span>'</span><span>,</span><span> '</span><span>Thanks for your subscription</span><span>'</span><span>],
</span></span><span><span>  [</span><span>'</span><span>Cancellation</span><span>'</span><span>,</span><span> '</span><span>You have been unsubscribed successfully</span><span>'</span><span>]
</span></span><span><span>])
</span></span><span><span>
</span></span><span><span>const</span><span> higherOrderStepFiveWithMap</span><span> =</span><span> lookupMap</span><span> =&gt;</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>lookupMap</span><span>.</span><span>has</span><span>(</span><span>response</span><span>.</span><span>type</span><span>))</span><span> {
</span></span><span><span>    return</span><span> lookupMap</span><span>.</span><span>get</span><span>(</span><span>response</span><span>.</span><span>type</span><span>)
</span></span><span><span>  }
</span></span><span><span>}
</span></span><span><span>
</span></span><span><span>const</span><span> stepFiveWithMap</span><span> =</span><span> higherOrderStepFiveWithMap</span><span>(</span><span>responseMap</span><span>)
</span></span><span><span>
</span></span><span><span>// Now use stepFiveWithMap(yourResponseObject)
</span></span></code><!--]--></pre><p><!--[-->While I’d prefer this version because of the reusability, purity and low coupling, let’s stick to the simple and impure map version:<!--]--></p><pre><!--[--><code>const lookupMap = new Map([
  ['Confirmation', 'Thanks for your subscription'],
  ['Cancellation', 'You have been unsubscribed successfully']
])

const simpleAndImpureStepFiveWithMap = response =&gt; {
  if (!response.valid) {
    return 'The provided code is invalid'
  }

  if (response.used) {
    return 'This token has already been used'
  }

  if (lookupMap.has(response.type)) {
    return lookupMap.get(response.type)
  }
}
</code><!--]--></pre><h3><a><!--[-->Sixth iteration - Invalid state<!--]--></a></h3><h4><a><!--[-->Motivation<!--]--></a></h4><p><!--[-->Alright! The code looks readable, has no duplication and is easily maintainable. One thing is missing though: Handling invalid states. Even if you think that this will never ever happen, it won’t hurt to throw an error if it does. In the best case, your monitoring/reporting tool will pick it up and tell you. It’s worth to write tests for invalid states as well, especially when it comes to user input!<!--]--></p><h4><a><!--[-->Code<!--]--></a></h4><pre><!--[--><code><span><span>const</span><span> lookupMap</span><span> =</span><span> new</span><span> Map</span><span>([
</span></span><span><span>  [</span><span>'</span><span>Confirmation</span><span>'</span><span>,</span><span> '</span><span>Thanks for your subscription</span><span>'</span><span>],
</span></span><span><span>  [</span><span>'</span><span>Cancellation</span><span>'</span><span>,</span><span> '</span><span>You have been unsubscribed successfully</span><span>'</span><span>]
</span></span><span><span>])
</span></span><span><span>
</span></span><span><span>const</span><span> simpleAndImpureStepFiveWithMap</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>lookupMap</span><span>.</span><span>has</span><span>(</span><span>response</span><span>.</span><span>type</span><span>))</span><span> {
</span></span><span><span>    return</span><span> lookupMap</span><span>.</span><span>get</span><span>(</span><span>response</span><span>.</span><span>type</span><span>)
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  throw</span><span> new</span><span> Error</span><span>(</span><span>'</span><span>Invalid state while evaluating response</span><span>'</span><span>)
</span></span><span><span>
</span></span><span><span>  /*
</span></span><span><span>    we could also 'shorten' that a little with short-circuit:
</span></span><span><span>
</span></span><span><span>    const invalid = () =&gt; { throw new Error('Invalid state while evaluating response') }
</span></span><span><span>
</span></span><span><span>    return lookupMap.get(response.type) || invalid()
</span></span><span><span>  */
</span></span><span><span>}
</span></span></code><!--]--></pre><h2><a><!--[-->Wrapping it up<!--]--></a></h2><p><!--[--><strong><!--[-->We did it!<!--]--></strong> Before wrapping everything up, let’s compare the initial and the final code:<!--]--></p><p><!--[--><strong><!--[-->Initial code<!--]--></strong><!--]--></p><pre><!--[--><code><span><span>const</span><span> evaluateResponseOld</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  let</span><span> text</span><span> =</span><span> ''
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Confirmation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>Thanks for your subscription</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }</span><span> else</span><span> if</span><span> (</span><span>response</span><span>.</span><span>type</span><span> ===</span><span> '</span><span>Cancellation</span><span>'</span><span>)</span><span> {
</span></span><span><span>      if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>      }</span><span> else</span><span> {
</span></span><span><span>        text</span><span> =</span><span> '</span><span>You have been unsubscribed successfully</span><span>'
</span></span><span><span>      }
</span></span><span><span>    }
</span></span><span><span>  }</span><span> else</span><span> {
</span></span><span><span>    text</span><span> =</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>  return</span><span> text
</span></span><span><span>}
</span></span></code><!--]--></pre><p><!--[--><em><!--[--><em><!--[-->Final<!--]--></em> code<!--]--></em><!--]--></p><pre><!--[--><code><span><span>const</span><span> lookupMap</span><span> =</span><span> new</span><span> Map</span><span>([
</span></span><span><span>  [</span><span>'</span><span>Confirmation</span><span>'</span><span>,</span><span> '</span><span>Thanks for your subscription</span><span>'</span><span>],
</span></span><span><span>  [</span><span>'</span><span>Cancellation</span><span>'</span><span>,</span><span> '</span><span>You have been unsubscribed successfully</span><span>'</span><span>]
</span></span><span><span>])
</span></span><span><span>
</span></span><span><span>const</span><span> simpleAndImpureStepFiveWithMap</span><span> =</span><span> response</span><span> =&gt;</span><span> {
</span></span><span><span>  if</span><span> (</span><span>!</span><span>response</span><span>.</span><span>valid</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>The provided code is invalid</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>response</span><span>.</span><span>used</span><span>)</span><span> {
</span></span><span><span>    return</span><span> '</span><span>This token has already been used</span><span>'
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  if</span><span> (</span><span>lookupMap</span><span>.</span><span>has</span><span>(</span><span>response</span><span>.</span><span>type</span><span>))</span><span> {
</span></span><span><span>    return</span><span> lookupMap</span><span>.</span><span>get</span><span>(</span><span>response</span><span>.</span><span>type</span><span>)
</span></span><span><span>  }
</span></span><span><span>
</span></span><span><span>  throw</span><span> new</span><span> Error</span><span>(</span><span>'</span><span>Invalid state while evaluating response</span><span>'</span><span>)
</span></span><span><span>}
</span></span></code><!--]--></pre><h2><a><!--[-->Conclusion<!--]--></a></h2><p><!--[-->As you see, the final code is has a maximum depth of one (previously four), only three branches (previously five) and is <em><!--[-->even shorter<!--]--></em> than the initial one (five source lines of code less). The cyclomatic complexity, which measures the amount of independent paths the function can take on execution, decreased by two.<!--]--></p><p><!--[-->The techniques utilized in a nutshell:<!--]--></p><ol><!--[--><li><!--[-->Remove temporary variables and try to avoid shared state where you can<!--]--></li><li><!--[-->Invert guarding ifs and use early returns to reduce cognitive load and complexity/depth<!--]--></li><li><!--[-->Remove else if you always return early<!--]--></li><li><!--[-->Find and deal with duplicated code in leftover logic<!--]--></li><li><!--[-->Use a lookup table/lookup object when you can<!--]--></li><li><!--[-->Proper handling of invalid state<!--]--></li><!--]--></ol><p><!--[-->I hope you liked the content! If so it would be neat if you could spread the word (f.ex. with the buttons below).<!--]--></p><p><!--[--><strong><!--[-->Questions left? Critics? What is your opinion on those techniques?<!--]--></strong><!--]--></p><p><!--[-->Hit me up on Twitter (<a><!--[--><!--[-->@TheAlexLichter<!--]--><!--]--></a>) or write me a mail (blog at lichter dot io).<!--]--></p><style>html pre.shiki code .s_wWq, html code.shiki .s_wWq{--shiki-default:#CB7676}html pre.shiki code .sCK9x, html code.shiki .sCK9x{--shiki-default:#80A665}html pre.shiki code .s_pn2, html code.shiki .s_pn2{--shiki-default:#666666}html pre.shiki code .st-jp, html code.shiki .st-jp{--shiki-default:#BD976A}html pre.shiki code .sNJcY, html code.shiki .sNJcY{--shiki-default:#C98A7D77}html pre.shiki code .s3QIE, html code.shiki .s3QIE{--shiki-default:#4D9375}html pre.shiki code .s7rlk, html code.shiki .s7rlk{--shiki-default:#C98A7D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sm68I, html code.shiki .sm68I{--shiki-default:#B8A965}html pre.shiki code .sux-A, html code.shiki .sux-A{--shiki-default:#758575DD}</style>]]></content:encoded>
        </item>
    </channel>
</rss>