Designing Secure Authentication and Identity Management

Editor's Note: The following is an article written for and published in DZone's 2021 Application Security Trend Report.


Organizations and individuals face an ever-increasing threat from a wide variety of actors. Threats can come from nation states, organized crime gangs, or even determined individuals. These attacks come in the forms of ransomware, which can cripple your business and cause data loss or data exfiltration. Beyond ransomware, more insidious attacks like the SolarWinds supply chain attack can impact a large number of organizations beyond the initial attack victim. A supply chain attack looks for weak links in the business process — in this case, pursuing a network monitoring vendor whose software is widely used and inherently needs to run with high privileges. 

Comparing Methods for Appending and Inserting With JavaScript

Let’s say we want to add something to a webpage after the initial load. JavaScript gives us a variety of tools. Perhaps you’ve used some of them, like append, appendChild, insertAdjacentHTML, or innerHTML.

The difficult thing about appending and inserting things with JavaScript isn’t so much about the tools it offers, but which one to use, when to use them, and understanding how each one works.

Let’s try to clear things up.

Super quick context

It might be helpful to discuss a little background before jumping in. At the simplest level, a website is an HTML file downloaded from a server to a browser.

Your browser converts the HTML tags inside your HTML file into a bunch of objects that can be manipulated with JavaScript. These objects construct a Document Object Model (DOM) tree. This tree is a series of objects that are structured as parent-child relationships.

In DOM parlance, these objects are called nodes, or more specifically, HTML elements.

<!-- I'm the parent element -->
<div>
  <!-- I'm a child element -->
  <span>Hello</span>
</div>

In this example, the HTML span element is the child of the div element, which is the parent.

And I know that some of these terms are weird and possibly confusing. We say “node”, but other times we may say “element” or “object” instead. And, in some cases, they refer to the same thing, just depending on how specific we want to be .

For example, an “element” is a specific type of “node”, just like an apple is a specific type of fruit.

We can organize these terms from most general, to most specific: ObjectNodeElementHTML Element

Understanding these DOM items is important, as we’ll interact with them to add and append things with JavaScript after an initial page load. In fact, let’s start working on that.

Setup

These append and insert methods mostly follow this pattern:

Element.append_method_choice(stuff_to_append)

Again, an element is merely an object in the DOM Tree that represents some HTML. Earlier, we had mentioned that the purpose of the DOM tree is to give us a convenient way to interact with HTML using JavaScript.

So, how do we use JavaScript to grab an HTML element?

Querying the DOM

Let’s say we have the following tiny bit of HTML:

<div id="example" class="group">
  Hello World
</div>

There are a few common ways to query the DOM:

// Query a specific selector (could be class, ID, element type, or attribute):
const my_element1 = document.querySelector('#example')

// Query an element by its ID:
const my_element2 = document.getElementbyId('example')

// Query an element by its class:
const my_element3 = document.getElementbyClass('group')[0] 

In this example, all three lines query the same thing, but look for it in different ways. One looks at any of the item’s CSS selectors; one looks at the item’s ID; and one looks at the item’s class.

Note that the getElementbyClass method returns an array. That’s because it’s capable of matching multiple elements in the DOM and storing those matches in an array makes sure all of them are accounted for.

What we can append and insert

// Append Something
const my_element1 = document.querySelector('#example')
my_element1.append(something)

In this example, something is a parameter that represents stuff we want to tack on to the end of (i.e. append to) the matched element.

We can’t just append any old thing to any old object. The append method only allows us to append either a node or plain text to an element in the DOM. But some other methods can append HTML to DOM elements as well.

  1. Nodes are either created with document.createElement() in JavaScript, or they are selected with one of the query methods we looked at in the last section.
  2. Plain text is, well, text. It’s plain text in that it does not carry any HTML tags or formatting with it. (e.g. Hello).
  3. HTML is also text but, unlike plain text, it does indeed get parsed as markup when it’s added to the DOM (e.g. <div>Hello</div>).

It might help to map out exactly which parameters are supported by which methods:

MethodNodeHTML TextText
appendYesNoYes
appendChildYesNoNo
insertAdjacentHTMLNoYesYes1
innerHTML2NoYesYes
1 This works, but insertAdjacentText is recommended.
2 Instead of taking traditional parameters, innerHTML is used like: element.innerHTML = 'HTML String'

How to choose which method to use

Well, it really depends on what you’re looking to append, not to mention certain browser quirks to work around.

  • If you have existing HTML that gets sent to your JavaScript, it’s probably easiest to work with methods that support HTML.
  • If you’re building some new HTML in JavasScript, creating a node with heavy markup can be cumbersome, whereas HTML is less verbose.
  • If you want to attach event listeners right away, you’ll want to work with nodes because we call addEventListener on nodes, not HTML.
  • If all you need is text, any method supporting plain text parameters is fine.
  • If your HTML is potentially untrustworthy (i.e. it comes from user input, say a comment on a blog post), then you’ll want to be careful when using HTML, unless it has been sanitized (i.e. the harmful code has been removed).
  • If you need to support Internet Explorer, then using append is out of the question.

Example

Let’s say we have a chat application, and we want to append a user, Dale, to a buddy list when they log in.

<!-- HTML Buddy List -->
<ul id="buddies">
  <li><a>Alex</a></li>
  <li><a>Barry</a></li>
  <li><a>Clive</a></li>
  <!-- Append next user here -->
</ul>

Here’s how we’d accomplish this using each of the methods above.

append

We need to create a node object that translates to <li><a>Dale</a></li>.

const new_buddy = document.createElement('li')
const new_link = document.createElement('a')

const buddy_name = "Dale"

new_link.append(buddy_name) // Text param
new_buddy.append(new_link) // Node param

const list = document.querySelector('#buddies')
list.append(new_buddy) // Node param

Our final append places the new user at the end of the buddy list, just before the closing </ul> tag. If we’d prefer to place the user at the front of the list, we could use the prepend method instead.

You may have noticed that we were also able to use append to fill our <a> tag with text like this:

const buddy_name = "Dale"
new_link.append(buddy_name) // Text param

This highlights the versatility of append.

And just to call it out once more, append is unsupported in Internet Explorer.

appendChild

appendChild is another JavaScript method we have for appending stuff to DOM elements. It’s a little limited in that it only works with node objects, so we we’ll need some help from textContent (or innerText) for our plain text needs.

Note that appendChild, unlike append, is supported in Internet Explorer.

const new_buddy = document.createElement('li')
const new_link = document.createElement('a')

const buddy_name = "Dale"

new_link.textContent = buddy_name
new_buddy.appendChild(new_link) // Node param

const list = document.querySelector('#buddies')
list.appendChild(new_buddy) // Node param

Before moving on, let’s consider a similar example, but with heavier markup.

Let’s say the HTML we wanted to append didn’t look like <li><a>Dale</a></li>, but rather:

<li class="abc" data-tooltip="Click for Dale">
  <a id="user_123" class="def" data-user="dale">
    <img src="images/dale.jpg" alt="Profile Picture"/>
    <span>Dale</span>
  </a>
</li>

Our JavaScript would look something like:

const buddy_name = "Dale"

const new_buddy = document.createElement('li')
new_buddy.className = ('abc')
new_buddy.setAttribute('data-tooltip', `Click for ${buddy_name}`)

const new_link = document.createElement('a')
new_link.id = 'user_123'
new_link.className = ('def')
new_link.setAttribute('data-user', buddy_name)

const new_profile_img = document.createElement('img')
new_profile_img.src = 'images/dale.jpg'
new_profile_img.alt = 'Profile Picture'

const new_buddy_span = document.createElement('span')
new_buddy_span.textContent = buddy_name

new_link.appendChild(new_profile_img) // Node param
new_link.appendChild(new_buddy_span) // Node param
new_buddy.appendChild(new_link) // Node param

const list = document.querySelector('#buddies')
list.appendChild(new_buddy) // Node param

There’s no need to follow all of above JavaScript – the point is that creating large amounts of HTML in JavaScript can become quite cumbersome. And there’s no getting around this if we use append or appendChild.

In this heavy markup scenario, it might be nice to just write our HTML as a string, rather than using a bunch of JavaScript methods…

insertAdjacentHTML

insertAdjacentHTML is is like append in that it’s also capable of adding stuff to DOM elements. One difference, though, is that insertAdjacentHTML inserts that stuff at a specific position relative to the matched element.

And it just so happens to work with HTML. That means we can insert actual HTML to a DOM element, and pinpoint exactly where we want it with four different positions:

<!-- beforebegin -->
<div id="example" class="group">
  <!-- afterbegin -->
  Hello World
  <!-- beforeend -->
</div>
<!-- afterend -->

So, we can sorta replicate the same idea of “appending” our HTML by inserting it at the beforeend position of the #buddies selector:

const buddy_name = "Dale"

const new_buddy = `<li><a>${buddy_name}</a></li>`
const list = document.querySelector('#buddies')
list.insertAdjacentHTML('beforeend', new_buddy)

Remember the security concerns we mentioned earlier. We never want to insert HTML that’s been submitted by an end user, as we’d open ourselves up to cross-site scripting vulnerabilities.

innerHTML

innerHTML is another method for inserting stuff. That said, it’s not recommended for inserting, as we’ll see.

Here’s our query and the HTML we want to insert:

const buddy_name = "Dale"
const new_buddy = `<li><a>${buddy_name}</a></li>`
const list = document.querySelector('#buddies')  
list.innerHTML += new_buddy

Initially, this seems to work. Our updated buddy list looks like this in the DOM:

<ul id="buddies">
  <li><a>Alex</a></li>
  <li><a>Barry</a></li>
  <li><a>Clive</a></li>
  <li><a>Dale</a></li>
</ul>

That’s what we want! But there’s a constraint with using innerHTML that prevents us from using event listeners on any elements inside of #buddies because of the nature of += in list.innerHTML += new_buddy.

You see, A += B behaves the same as A = A + B. In this case, A is our existing HTML and B is what we’re inserting to it. The problem is that this results in a copy of the existing HTML with the additional inserted HTML. And event listeners are unable to listen to copies. That means if we want to listen for a click event on any of the <a> tags in the buddy list, we’re going to lose that ability with innerHTML.

So, just a word of caution there.

Demo

Here’s a demo that pulls together all of the methods we’ve covered. Clicking the button of each method inserts “Dale” as an item in the buddies list.

Go ahead and open up DevTools while you’re at it and see how the new list item is added to the DOM.

Recap

Here’s a general overview of where we stand when we’re appending and inserting stuff into the DOM. Consider it a cheatsheet for when you need help figuring out which method to use.

MethodNode
HTML TextText
Internet Explorer?Event ListenersSecure?
HTML Templating
appendYesNoYesNoPreservesYesMedium
appendChildYesNoNoYesPreservesYesMedium
insertAdjacentHTMLNoYesYes1YesPreservesCarefulEasy
innerHTML2NoYesYesYesLosesCarefulEasy
1 This works, but insertAdjacentText is recommended.
2 Instead of taking traditional parameters, innerHTML is used like: element.innerHTML = 'HTML String'

If I had to condense all of that into a few recommendations:

  • Using innerHTML for appending is not recommended as it removes event listeners.
  • append works well if you like the flexibility of working with node elements or plain text, and don’t need to support Internet Explorer.
  • appendChild works well if you like (or need) to work with node elements, and want full browser coverage.
  • insertAdjacentHTML is nice if you need to generate HTML, and want to more specific control over where it is placed in the DOM.

Last thought and a quick plug :)

This post was inspired by real issues I recently ran into when building a chat application. As you’d imagine, a chat application relies on a lot of appending/inserting — people coming online, new messages, notifications, etc.

That chat application is called Bounce. It’s a peer-to-peer learning chat. Assuming you’re a JavaScript developer (among other things), you probably have something to teach! And you can earn some extra cash.

If you’re curious, here’s a link to the homepage, or my profile on Bounce. Cheers!


The post Comparing Methods for Appending and Inserting With JavaScript appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.

8 Best Help Desk Software for Small Business 2021 (Compared)

Are you looking for the best help desk software for your business?

Using help desk software can help you improve your customer support workflow, better manage requests, and improve your customer relationships.

In this article, we’ve hand picked some of the best help desk software you can use along with your WordPress site. We’re using these tools across our various businesses to help deliver a great customer experience.

8 best help desk software for small business (compared)

Why Use Help Desk Software?

Help desk software makes it easy to manage customer support requests to serve your customers better.

This leads to happier customers and improved customer retention, which means more money for your business over the long term.

When you’re just getting started online, it can be easy to manage all of your customer support requests yourself through your business email address.

But as your WordPress website continues to grow, it can be challenging to keep up the same quality of customer support.

Using help desk software in your business can elevate the quality of your customer interactions and give you a competitive advantage in your space.

The best help desk software:

  • Helps you scale your customer support as your business grows
  • Makes it easy to keep track of support requests across multiple channels
  • Lets you create detailed customer profiles and better understand your users
  • Improves support response time and customer relationships easily

That being said, let’s take a look at some of the best help desk software you can use to help your small business website grow.

1. Help Scout

Help Scout

Help Scout is one of the best all in one help desk software in the market. It has nearly every feature you need to streamline your help desk and customer support process.

We use Help Scout here at WPBeginner and our other businesses for all of our email support needs.

It can help you manage all of your chat and email support requests from one place. That way, your customers can reach out using the method they prefer, and your support team can manage requests from a single queue.

You can speed up support requests by created saved replies, so your team can respond to common questions instantly.

There’s a built-in knowledge base feature, so you can build your own help center. This reduces the total number of support requests since your users can find solutions to their problems.

You’ll also find in depth reporting data, so you can see where your team is doing great and what needs to be improved.

The time tracking report lets you see where your support team is spending the most time, so you can improve your product workflows.

A live chat feature, also known as Beacon, is available if you want to add an additional support channel.

It recommends articles to help solve your users solve problems. If they can’t find a solution, then they can chat with your team.

Pricing: The Basic plan starts at $20 per user per month and includes 3 mailboxes, live chat, and more. The Plus plan starts at $35 per user per month if you have a bigger team.

2. HelpDesk.com

HelpDesk.com

HelpDesk is a very easy to use help desk tool and ticketing software. It’s very intuitive and a simple way to keep all your messages in one place.

Any requests from contact forms, incoming emails, live chat, and more will come to a single dashboard, and a ticket will be created.

There are advanced ticketing, sorting, and tagging features, so you can prioritize the most important messages.

Plus, there are useful team collaboration tools like multiple mailboxes, agent groups, and private notes for support tickets to help your entire support team become more effective.

You can use the automation features like automated task assignments, canned responses, and custom automated workflows to optimize your response time.

Those running IT help desks can use the specialized IT service teams features to create groups of agents responsible for certain areas like subscriptions, signs up process, end-users, and more.

You can even create an IT support help desk for internal teams to help employees get up to speed on your software and processes.

There’s also an integration with LiveChat, so you can manage help desk tickets and solve customer issues in live chat all in one place.

You’ll find additional integrations like Hubspot, Salesforce, Zapier, Slack, and more that you can use to improve your support workflow.

Pricing: For teams, pricing starts at $19 per agent per month and includes a ticketing system, 60-day chat history, and more.

3. Hubspot

HubSpot

Hubspot offers business owners a wide range of customer management tools to help improve customer satisfaction and relationships.

It includes easy to use help desk software and a ticketing system to keep track of long term customer service requests.

They offer one of the best CRMs for small businesses that easily integrates with the customer service management software.

The service desk software and ticketing system organizes all of your support requests into a single dashboard that your entire team can access.

You can keep track of important support metrics like ticket volume, agent response time, and more. This helps you see if you’re hitting your goals and meeting your customer service level agreements (SLAs).

You’ll find additional features like knowledge base software to help your customers resolve their own issues and routing and automation to help save you time.

Plus, there’s bundled live chat and chatbot functionality, so you can communicate in real time with your customers.

Pricing: There’s a free plan for all users. Paid plans start at $45 per month and give you access to additional automation tools and support for more team members.

4. FreshDesk

FreshDesk

FreshDesk is another popular help desk provider for businesses. It’s very user friendly, while still offering plenty of advanced features.

The help desk system has a shared inbox for easy and fast team collaboration, escalation, and issue management. You can even route tasks based on team member availability.

Beyond help desk features, you’ll find chatbots, live chat, modern messaging, automation features, omnichannel support, and more.

There’s built in reporting and data, so you can refine your support processes over multiple communication channels. You can even use the social media integration to convert messages and brand mentions into tickets and respond.

It also includes various tools to help create your own self service portal like forum support, FAQ creation, a help widget, and more.

Pricing: The basic version of the software starts at $15 per month. While full omnichannel support plans start at $79 per month when billed per year.

There is a free version of the support software that supports an unlimited number of agents. But, it only includes ticketing and knowledge base features.

5. Nextiva

Nextiva

Nextiva is the best business phone service for small businesses. Beyond phone support, they offer a complete multichannel support solution.

The integrated help desk solution lets you communicate across many different channels from a single app including, phone, email, and team messaging.

No matter where your customers reach you from, you can respond from one place.

The ticket management system is straightforward to use, and you can set priorities, send reminders, message your team, and more.

If you’re using other Nextiva services like Nextiva business VoIP or the sales CRM, then these will instantly integrate.

You’ll find other useful features like call routing, canned responses, a mobile app, and a self-service knowledge base.

We use Nextiva at WPBeginner for all of our phone support needs. It’s the best option available if you need to offer phone support.

There are all kinds of powerful features to help your phone support team including, call forwarding, custom greetings, virtual business phone number, analytics, and more.

Nextiva works great for small business owners as well as large call centers who want to automate sales and customer support.

Pricing: Nextiva starts at $18.95 per month for between 20-99 users. If you want support for SMS and more integrations, then the Pro plan starts at $22.95 per month.

6. LiveChat

LiveChat

LiveChat is the best live chat software in the market. It lets you quickly add live chat support to your website, so you can instantly respond to customer’s requests.

The LiveChat apps are easy to use and work on mobile, desktop, and tablet devices across Android and iOS. So, your support team can answer requests without logging into the WordPress dashboard.

Plus, there’s a WordPress plugin that makes it easy to integrate with your website.

You can set up LiveChat to work during your non-work hours, so all live chat requests will go directly to your help desk management system.

We use LiveChat across all of our eCommerce businesses to support our pre-sales staff.

The chat window is very easy to customize to match your website’s branding.

One really great feature of this tool is the speed. The chat window loads faster than other providers and works across every device.

It integrates easily with other customer support and marketing tools you’re already using, like HelpDesk, HubSpot, and Google Analytics.

You’ll also find additional support tools to help improve the quality of your support, like visitor tracking, a smart API that integrates with your knowledge base, and more.

Pricing: There are plans for businesses of all sizes. The starter plan begins at $16 per agent per month when paid yearly. Plus, there’s a 14 day free trial to test out the service.

7. Chatbot.com

ChatBot.com

Chatbot.com is the best AI chatbot software in the market today. This tool makes it easy for small businesses to create their own AI chatbot and improve customer service.

You can use the drag and drop builder to create a customer support chatbot quickly.

There’s also a library of industry specific templates you can use. These will help you develop your unique chatbot based on user questions and scenarios.

It integrates easily with both WordPress and WooCommerce. Plus, your live chat and customer service software of choice.

We use ChatBot.com in several of our SaaS businesses to handle pre-sales questions and forward users to our live support team if necessary.

This process can make your customer service workflow more effective, and free up time for your support staff.

Pricing: It starts at $50 per month billed monthly for up to 1,000 monthly chats, and goes up from there.

There’s a 14-day free trial included in every plan, so you can see if chatbots work for your business.

8. WPForms

WPForms

WPForms is the best contact form plugin for WordPress used by over 4 million websites. It’s packed with features, while still being incredibly easy to use.

We use WPForms here on WPBeginner and across all of our other websites.

You can use the drag and drop builder to create a customer support form quickly, so your users can submit a support request to your team.

The free version of the plugin lets you build a basic contact form and includes spam protection, email notifications, and more.

The pro version of the plugin takes these features even further and turns it into a useful service desk tool. It lets you create more advanced forms with conditional logic, form abandonment functionality, geo-location, and more.

Every form submission goes directly to your WordPress dashboard, so you can quickly respond to customer queries. You can also set up instant form notifications that automatically send you an email when a user submits a form.

You can notify yourself or your team members in charge of customer support.

There’s also a user journey addon that lets you see what your visitor did on your website before submitting a form. This makes it easy to see where your user got stuck, so you can resolve their issue faster.

Plus, there are over 3000 different software integrations, including service desk, HR software, project management tools, marketing automation tools, and more.

Pricing: The Basic plan starts at $39.59 per year and has features for simple form creation, but to get access to user journey reports, advanced integrations, and more, the Pro plan is $199.50 per year.

What is the Best Help Desk Software (Expert Pick)?

In our expert opinion, there are several different help desk software solutions that can be the perfect choice for your business.

If you’re looking for the best email help desk that brings all of your customer support channels together, then Help Scout is the best option.

If you need a great all in one help desk tool that integrates perfectly with LiveChat, then HelpDesk is perfect.

If you want a customer help desk that also includes a business phone system, then Nextiva is a great choice.

Regardless of which help desk software you use, you’d want to use WPForms since it helps you forward the message from your website to the right help desk software.

Aside from the top help desk software on our list, we also looked at other providers like Zoho Desk, Jira, LiveAgent, FreshService, HappyFox, Zendesk support, and more.

However, we decided not to list them to help you avoid choice paralysis, so you can quickly find the best help desk software for you.

We hope this article helped you find the best help desk software for your business. You may also want to see our picks of the best email marketing services for small businesses and our guide on how to choose the best web design software.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post 8 Best Help Desk Software for Small Business 2021 (Compared) appeared first on WPBeginner.

Rebuilding A Large E-Commerce Website With Next.js (Case Study)

At our company, Unplatform, we have been building e-commerce sites for decades now. Over those years, we have seen the technology stack evolve from server-rendered pages with some minor JavaScript and CSS to full-blown JavaScript applications.

The platform we used for our e-commerce sites was based on ASP.NET and when visitors started to expect more interaction, we added React for the front-end. Although mixing the concepts of a server web framework like ASP.NET with a client-side web framework like React made things more complicated, we were quite happy with the solution. That was until we went to production with our highest traffic customer. From the moment we went live, we experienced performance issues. Core Web Vitals are important, even more so in e-commerce. In this Deloitte study: Milliseconds Make Millions, the investigators analyzed mobile site data of 37 different brands. As a result, they found that a 0.1s performance improvement can lead to a 10% increase in conversion.

To mitigate the performance issues, we had to add a lot of (unbudgeted) extra servers and had to aggressively cache pages on a reverse proxy. This even required us to disable parts of the site’s functionality. We ended up having a really complicated, expensive solution that in some cases just statically served some pages.

Obviously, this didn’t feel right, until we found out about Next.js. Next.js is a React-based web framework that allows you to statically generate pages, but you can also still use server-side rendering, making it ideal for e-commerce. It can be hosted on a CDN like Vercel or Netlify, which results in lower latency. Vercel and Netlify also use serverless functions for the Server Side Rendering, which is the most efficient way to scale out.

Challenges

Developing with Next.js is amazing, but there are definitely some challenges. The developer experience with Next.js is something you just need to experience. The code you write visualizes instantly in your browser and productivity goes through the sky. This is also a risk because you can easily get too focused on productivity and neglect the maintainability of your code. Over time, this and the untyped nature of JavaScript can lead to the degradation of your codebase. The number of bugs increases and productivity starts to go down.

It can also be challenging on the runtime side of things. The smallest changes in your code can lead to a drop in performance and other Core Web Vitals. Also, careless use of server-side rendering can lead to unexpected service costs.

Let’s have a closer look at our lessons learned in overcoming these challenges.

  1. Modularize Your Codebase
  2. Lint And Format Your Code
  3. Use TypeScript
  4. Plan For Performance And Measure Performance
  5. Add Performance Checks To Your Quality Gate
  6. Add Automated Tests
  7. Aggressively Manage Your Dependencies
  8. Use A Log Aggregation Service
  9. Next.js’s Rewrite Functionality Enables Incremental Adoption
Lesson Learned: Modularize Your Codebase

Front-end frameworks like Next.js make it so easy to get started these days. You just run npx create-next-app and you can start coding. But if you are not careful and start banging out code without thinking about design, you might end up with a big ball of mud.

When you run npx create-next-app, you will have a folder structure like the following (this is also how most examples are structured):

/public
  logo.gif
/src
  /lib
    /hooks
      useForm.js
  /api
     content.js
  /components
     Header.js
     Layout.js
  /pages
     Index.js

We started out using the same structure. We had some subfolders in the components folder for bigger components, but most of the components were in the root components folder. There is nothing wrong with this approach and it’s fine for smaller projects. However, as our project grew it became harder to reason about components and where they are used. We even found components that were no longer used at all! It also promotes a big ball of mud, because there is no clear guidance on what code should be dependent on what other code.

To solve this, we decided to refactor the codebase and group the code by functional modules (kind of like NPM modules) instead of technical concepts:

/src
  /modules 
    /catalog
      /components
        productblock.js
    /checkout
      /api
        cartservice.js
      /components
        cart.js

In this small example, there is a checkout module and a catalog module. Grouping the code this way leads to better discoverability: by merely looking at the folder structure you know exactly what kind of functionality is in the codebase and where to find it. It also makes it a lot easier to reason about dependencies. In the previous situation, there were a lot of dependencies between the components. We had pull requests for changes in the checkout that also impacted catalog components. This increased the number of merge conflicts and made it harder to make changes.

The solution that worked best for us was to keep the dependencies between the modules to an absolute minimum (if you really need a dependency, make sure its uni-directional) and introduce a “project” level that ties everything together:

/src
  /modules
    /common
      /atoms
      /lib 
    /catalog
      /components
        productblock.js
    /checkout
      /api
        cartservice.js
      /components
        cart.js
    /search
  /project
    /layout
      /components
    /templates
      productdetail.js
      cart.js
  /pages
    cart.js

A visual overview of this solution:

The project level contains the code for the layout of the e-commerce site and page templates. In Next.js, a page component is a convention and results in a physical page. In our experience, these pages often need to reuse the same implementation and that is why we have introduced the concept of “page templates”. The page templates use the components from the different modules, for example, the product detail page template will use components from the catalog to display product information, but also an add to cart component from the checkout module.

We also have a common module, because there is still some code that needs to be reused by the functional modules. It contains simple atoms that are React components used to provide a consistent look and feel. It also contains infrastructure code, think of certain generic react hooks or GraphQL client code.

Warning: Make sure the code in the common module is stable and always think twice before adding code here, in order to prevent tangled code.

Micro Front-Ends

In even bigger solutions or when working with different teams, it can make sense to split up the application even more into so-called micro-frontends. In short, this means splitting up the application even more into multiple physical applications that are hosted independently on different URLs. For example: checkout.mydomain.com and catalog.mydomain.com. These are then integrated by a different application that acts as a proxy.

Next.js’ rewrite functionality is great for this and using it like this is supported by so-called Multi Zones.

The benefit of multi-zones is that every zone manages its own dependencies. It also makes it easier to incrementally evolve the codebase: If a new version of Next.js or React gets out, you can upgrade the zones one by one instead of having to upgrade the entire codebase at once. In a multi-team organization, this can greatly reduce dependencies between teams.

Further Reading

Lesson Learned: Lint And Format Your Code

This is something we learned in an earlier project: if you work in the same codebase with multiple people and don’t use a formatter, your code will soon become very inconsistent. Even if you are using coding conventions and are doing reviews, you will soon start to notice the different coding styles, giving a messy impression of the code.

A linter will check your code for potential issues and a formatter will make sure the code is formatted in a consistent way. We use ESLint & prettier and think they are awesome. You don’t have to think about the coding style, reducing the cognitive load during development.

Fortunately, Next.js 11 now supports ESLint out of the box (https://nextjs.org/blog/next-11), making it super easy to set up by running npx next lint. This saves you a lot of time because it comes with a default configuration for Next.js. For example, it is already configured with an ESLint extension for React. Even better, it comes with a new Next.js-specific extension that will even spot issues with your code that could potentially impact the Core Web Vitals of your application! In a later paragraph, we will talk about quality gates that can help you to prevent pushing code to a product that accidentally hurts your Core Web Vitals. This extension gives you feedback a lot faster, making it a great addition.

Further Reading

Lesson Learned: Use TypeScript

As components got modified and refactored, we noticed that some of the component props were no longer used. Also, in some cases, we experienced bugs because of missing or incorrect types of props being passed into the components.

TypeScript is a superset of JavaScript and adds types, which allows a compiler to statically check your code, kind of like a linter on steroids.

At the start of the project, we did not really see the value of adding TypeScript. We felt it was just an unnecessary abstraction. However, one of our colleagues had good experiences with TypeScript and convinced us to give it a try. Fortunately, Next.js has great TypeScript support out of the box and TypeScript allows you to add it to your solution incrementally. This means you don’t have to rewrite or convert your entire codebase in one go, but you can start using it right away and slowly convert the rest of the codebase.

Once we started migrating components to TypeScript, we immediately found issues with wrong values being passed into components and functions. Also, the developer feedback loop got shorter and you get notified of issues before running the app in the browser. Another big benefit we found is that it makes it a lot easier to refactor code: it is easier to see where code is being used and you immediately spot unused component props and code. In short, the benefits of TypeScript:

  1. Reduces the number of bugs
  2. Makes it easier to refactor your code
  3. Code gets easier to read

Further Reading

Lesson Learned: Plan For Performance And Measure Performance

Next.js supports different types of pre-rendering: Static generation and Server-side rendering. For best performance, it is recommended to use static generation, which happens during build time, but this is not always possible. Think of product detail pages that contain stock information. This kind of information changes often and running a build every time does not scale well. Fortunately, Next.js also supports a mode called Incremental Static Regeneration (ISR), which still statically generates the page, but generates a new one in the background every x seconds. We have learned that this model works great for larger applications. Performance is still great, it requires less CPU time than Server-side rendering and it reduces build times: pages only get generated on the first request. For every page you add, you should think of the type of rendering needed. First, see if you can use static generation; if not, go for Incremental Static Regeneration, and if that too is not possible, you can still use server-side rendering.

Next.js automatically determines the type of rendering based on the absence of getServerSideProps and getInitialProps methods on the page. It’s easy to make a mistake, which could cause the page to be rendered on the server instead of being statically generated. The output of a Next.js build shows exactly which page uses what type of rendering, so be sure to check this. It also helps to monitor production and track the performance of the pages and the CPU time involved. Most hosting providers charge you based on the CPU time and this helps to prevent any unpleasant surprises. I will describe how we monitor this in the Lesson learned: Use a log aggregation service paragraph.

Bundle Size

To have a good performance it is crucial to minimize the bundle size. Next.js has a lot of features out of the box that help, e.g. automatic code splitting. This will make sure that only the required JavaScript and CSS are loaded for every page. It also generates different bundles for the client and for the server. However, it is important to keep an eye on these. For example, if you import JavaScript modules the wrong way the server JavaScript can end up in the client bundle, greatly increasing the client bundle size and hurting performance. Adding NPM dependencies can also greatly impact the bundle size.

Fortunately, Next.js comes with a bundles analyzer that gives you insight into which code takes up what part of the bundles.

Further Reading

Lesson Learned: Add Performance Checks To Your Quality Gate

One of the big benefits of using Next.js is the ability to statically generate pages and to be able to deploy the application to the edge (CDN), which should result in great performance and Web Vitals. We learned that, even with great technology like Next.js, getting and keeping a great lighthouse score is really hard. It happened a number of times that after we deployed some changes to production, the lighthouse score dropped significantly. To take back control, we have added automatic lighthouse tests to our quality gate. With this Github Action you can automatically add lighthouse tests to your pull requests. We are using Vercel and every time a pull request is created, Vercel deploys it to a preview URL and we use the Github action to run lighthouse tests against this deployment.

If you don’t want to set up the GitHub action yourself, or if you want to take this even further, you could also consider a third-party performance monitoring service like DebugBear. Vercel also offers an Analytics feature, which measures the core Web Vitals of your production deployment. Vercel Analytics actually collects the measures from the devices of your visitors, so these scores are really what your visitors are experiencing. At the time of writing, Vercel Analytics only works on production deployments.

Lesson Learned: Add Automated Tests

When the codebase gets bigger it gets harder to determine if your code changes might have broken existing functionality. In our experience, it is vital to have a good set of End-to-end tests as a safety net. Even if you have a small project, it can make your life so much easier when you have at least some basic smoke tests. We have been using Cypress for this and absolutely love it. The combination of using Netlify or Vercel to automatically deploy your Pull request on a temporary environment and running your E2E tests is priceless.

We use cypress-io/GitHub-action to automatically run the cypress tests against our pull requests. Depending on the type of software you're building it can be valuable to also have more granular tests using Enzyme or JEST. The tradeoff is that these are more tightly coupled to your code and require more maintenance.

Lesson Learned: Aggressively Manage Your Dependencies

Managing dependencies becomes a time-consuming, but oh so important activity when maintaining a large Next.js codebase. NPM made adding packages so easy and there seems to be a package for everything these days. Looking back, a lot of times when we introduced a new bug or had a drop in performance it had something to do with a new or updated NPM package.

So before installing a package you should always ask yourself the following:

  • What is the quality of the package?
  • What will adding this package mean for my bundle size?
  • Is this package really necessary or are there alternatives?
  • Is the package still actively maintained?

To keep the bundle size small and to minimize the effort needed to maintain these dependencies it is important to keep the number of dependencies as small as possible. Your future self will thank you for it when you are maintaining the software.

Tip: The Import Cost VSCode extension automatically shows the size of imported packages.

Keep Up With Next.js Versions

Keeping up with Next.js & React is important. Not only will it give you access to new features, but new versions will also include bug fixes and fixes for potential security issues. Fortunately, Next.js makes upgrading incredibly easy by providing Codemods (https://nextjs.org/docs/advanced-features/codemods. These are automatic code transformations that automatically update your code.

Update Dependencies

For the same reason, it is important to keep the Next.js and React versions actual; it is also important to update other dependencies. Github’s dependabot (https://github.com/dependabot) can really help here. It will automatically create Pull Requests with updated dependencies. However, updating dependencies can potentially break things, so having automated end-to-end tests here can really be a lifesaver.

Lesson learned: Use A Log Aggregation Service

To make sure the app is behaving properly and to preemptively find issues, we have found it is absolutely necessary to configure a log aggregation service. Vercel allows you to log in and view the logs, but these are streamed in real-time and are not persisted. It also does not support configuring alerts and notifications.

Some exceptions can take a long time to surface. For example, we had configured Stale-While-Revalidate for a particular page. At some point, we noticed that the pages were not being refreshed and that old data was being served. After checking the Vercel logging, we found that an exception was happening during the background rendering of the page. By using a log aggregation service and configuring an alert for exceptions, we would have been able to spot this a lot sooner.

Log aggregation services can also be useful to monitor the limits of Vercel’s pricing plans. Vercel’s usage page also gives you insights in this, but using a log aggregation service allows you to add notifications when you reach a certain threshold. Prevention is better than cure, especially when it comes to billing.

Vercel offers a number of out-of-the-box integrations with log aggregation services, featuring Datadog, Logtail, Logalert, Sentry, and more.

Further Reading

Lesson Learned: Next.js’s Rewrite Functionality Enables Incremental Adoption

Unless there are some serious issues with the current website, not a lot of customers are going to be excited to rewrite the entire website. But what if you could start with rebuilding only the pages that matter most in terms of Web Vitals? That is exactly what we did for another customer. Instead of rebuilding the entire site, we only rebuild the pages that matter most for SEO and conversion. In this case the product detail and category pages. By rebuilding those with Next.js, performance greatly increased.

Next.js rewrite functionality is great for this. We built a new Next.js front-end that contains the catalog pages and deployed that to the CDN. All other existing pages are rewritten by Next.js to the existing website. This way you can start having the benefits of a Next.js site in a low-effort or low-risk manner.

Further Reading

What’s Next?

When we released the first version of the project and started doing serious performance testing we were thrilled by the results. Not only were the page response times and Web Vitals so much better than before, but the operational costs were also a fraction of what it was before. Next.js and JAMStack generally allow you to scale out in the most cost-efficient way.

Switching over from a more back-end-oriented architecture to something like Next.js is a big step. The learning curve can be quite steep, and initially, some team members really felt outside of their comfort zone. The small adjustments we made, the lessons learned from this article, really helped with this. Also, the development experience with Next.js gives an amazing productivity boost. The developer feedback cycle is incredibly short!

Further Reading