Skip to content

How to add Dark Mode in your Web Application

How to add Dark Mode in your Web Application

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

How to Create Dark/Light Mode in your JavaScript project?

Providing our visitors with the ability to toggle between white and dark themes has become one of the most important features of all time. In this article, we will cover the requirements to develop a persistent theme for your website successfully.

The code provided as part of this article will be technology agnostic and can be used with all major front-end frameworks such as Vue, React, or Svelte.

TL;DR If you are in a rush and just want access to the code, you can grab it from this Stackblitz: Light / Dark theme functionality in Vanilla JS.

What is White/Dark Mode

Before we jump into the code, we should first explain what the meaning behind the feature name "White/Dark Mode is".

This feature was first introduced in our Operating system by allowing users to change their default theme into Dark Mode, After this, Dark Mode has slowly made its way into our most used applications and websites as users expected to have a consistent look from their native application to Web App equivalent.

Providing "White/Dark mode" in your site means giving the user the ability to toggle their theme to either align to the Operating system theme settings or override it from the Web App UI.

White/Dark mode example

The above example shows the feature on thisdot.co triggered by the little toggle button available within the main navbar.

Implementing Dark Mode with Javascript

This feature has two main implementation levels. The first is the ability to assign the theme directly from the Operating System preferences, while the second combines the feature of the first plus the ability to override that feature from the UI.

Some websites just offer the first option and allow the site to emulate the OS preferences, even if this is mostly fine for most users, in this post, we will develop the second option as it is the most complicated approach and, therefore the most complete.

The development of this feature is going to be divided into the following steps:

  • Ensure CSS variables drive our application
  • Create a second set of variables for a dark theme
  • Create logic to read user preferences
  • Create logic to override the theme preference
  • Make our theme selection persistent

Let's dive into the code with our first step and initialize our application.

Move all styles into CSS variables

We are going to start our application by initializing a plain VanillaJs Stackblitz. This creates a simple project with an index.js file that includes our HTML and a style.css file that includes our global styles.

To be able to have a good starting point for our post, we are going to add some more elements on the main page of the app and then define some CSS variables to make our example more appealing.

First, we are going to clean our index.js and just leave the style import:

// Import stylesheets
import './style.css';

Then, we are going to change our index.html with the following content:

<body>
 <span>
   <h1>Heading 1</h1>
   <p>Paragraph</p>
 </span>
 <picture>
   <img width="100" src="https://www.thisdot.co/img/header-light-theme.webp">
 </picture>
</body>

And finally, define some CSS variables and styles in style.css:

:root{
 --text-primary:red;
 --text-secondary: grey;
 --background-color: white;
}
body{
 display: flex;
 background-color: var(--background-color);
}
h1 {
 color: var(--text-primary);
}
p {
 color: var(--text-secondary);
}

If you are new to CSS variables, you can read more about them in the MDN docs: Using CSS custom properties. There is one point to clarify regarding CSS variables and their naming convention. Because the color held within our variables will change (for example from red to blue), the variable name should be based on the color "role" (eg primary, shadow, border") and not on the actual color "blue, green".

In our example, we can see this naming convention in action as the variables are called "primary", "secondary" and "background".

The above should output a very simple but colorful design:

Example site on white theme

Create a second set of variables for the dark theme

Now that our basic application is set, it is time to create a second set of variables to be able to define the difference between the light and dark theme.

As you may notice, the CSS variables are not a must for this to work, but having all the variables set makes the maintenance of the site much easier than having to update color and styles scattered around.

We are returning to our style.css file and updating a few of the CSS properties. These variables will just be updated if the body has a specific class dark associated with it:

:root {
 --text-primary: red;
 --text-secondary: grey;
 --background-color: white;
}
body.dark {
 --text-primary: white;
 --background-color: black;
}

As the above code shows, because we are just overriding the variables, we do not actually need to redeclare them all and we can just declare the variables that we wish to update.

If we would go back and add a class of "dark" to our body in index.html we would see the following output: Example site on dark theme

As you can see, both white themes are fully set up and working. All that is left is creating the logic that can handle the theme toggle, but before we do so, there is one more change to add to our CSS.

In fact, some components, such as form controls and scrollbar, use a property called "color-scheme" to change their theme, to give this a try, we will add an in our HTML:

<body class="dark">
 <span>
   <h1>Heading 1</h1>
   <p>Paragraph</p>
   <input type="text" />
 </span>
 <picture>
   <img width="100" src="https://www.thisdot.co/img/header-light-theme.webp" />
 </picture>
</body>

Then, let's see how this would look in our design:

Input field displaying white theme while the rest of the app is on dark theme

The input is showing a white theme even if the dark theme is selected, and this is happening because we are not changing the "color-scheme" that is what is used for the form input.

Let's fix this by adding another CSS variable and a CSS declaration within our body:

:root {
 --text-primary: red;
 --text-secondary: grey;
 --background-color: white;
 --color-scheme: light;
}
body.dark {
 --text-primary: white;
 --background-color: black;
 --color-scheme: dark;
}
body {
 display: flex;
 background-color: var(--background-color);
 color-scheme: var(--color-scheme);
}
h1 {
 color: var(--text-primary);
}
p {
 color: var(--text-secondary);
}

With the above code, the "color-scheme" variable will be changed to align with our overall theme, as shown below: Input field showing dark theme

Note that settings can be overridden on a component-by-component level. In fact, if you would like to always display the white theme for a button or field, you can just redeclare the "color-scheme" and apply it to the CSS selection of your choice.

It is now time to move forward and include some JS to make the toggling automatic.

Create logic to read user preferences

It is now time to write some JavaScript and make our application dynamic. In this first logical step, we are going to read our Operating System color preference and use it to toggle the class assigned to our body.

To do so, we are going to use the "matchMedia" method to read the "prefer-color-scheme" just like shown in the code below:

const readSystemColorPreferences = () => {
 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
   return "dark";
 } else {
   return "light";
 }
}

The next step is to assign or remove the class from our body depending on the preferred scheme:

var preferredScheme = readSystemColorPreferences();

const updateScheme = (overriddenScheme) => {
   if( preferredScheme === "dark" ) {
   document.querySelector('body').classList.add("dark");
   } else {
   document.querySelector('body').classList.remove("dark");
   }
};
updateScheme();

Let's see if this works. If you are on Mac you can change the preferences by following the following steps:

  1. System settings
  2. Appearance
  3. Change the appearance
System appearance settings Mac OS

If you change the preferences and refresh your application, you should be able to see the application changing on the fly.

Create logic to override the theme preference

Having to change the full OS just for a single Website theme preference may be a little bit too much. So, in this step, we are going to enhance our logic to be able to override the preferences.

First, we are going to add a simple button in the HTML called Toggle Theme:

<body class="dark">
 <span>
   <h1>Heading 1</h1>
   <p>Paragraph</p>
   <input type="text" />
   <button>Toggle theme</button>
 </span>
 <picture>
   <img width="100" src="https://www.thisdot.co/img/header-light-theme.webp" />
 </picture>
 <button>Toggle theme</button>
</body>

Now, it is time to update the logic. First, we need to allow a way to override the preferences. We can do this by adding an argument to our updateScheme function:

let selectedTheme;
const updateScheme = (overriddenScheme) => {
 const preferredScheme = readSystemColorPreferences();
 selectedTheme = overriddenScheme || preferredScheme;
 localStorage.setItem("selectedTheme", selectedTheme);
 if (selectedTheme === 'dark') {
   document.querySelector('body').classList.add('dark');
 } else {
   document.querySelector('body').classList.remove('dark');
 }
};
updateScheme();

The above code just adds a global variable called selectedTheme and then, as we previously mentioned, adds a parameter of overrideScheme to our method and finally makes sure we use this variable if available selectedTheme = overriddenScheme || preferredScheme.

The next step requires us to be able to update our selectedTheme on a button click:

document.querySelector('button').addEventListener('click', () => {
 var newScheme = selectedTheme === 'dark' ? 'light' : 'dark';
 updateScheme(newScheme);
});

With the above code added to our application, we could override the system preferences, but unfortunately, our work is not completed. The logic we wrote above is not persistent, and the theme selection will be lost if we refresh the page.

Make our theme selection persistent

In this last step, we are going to save our selectedTheme in localStorage using the documentation provided in the MDN documentation: Local Storage.

First, let's save the selectedTheme into local storage by using localStorage.setItem:

const updateScheme = (overriddenScheme) => {
 const preferredScheme = readSystemColorPreferences();
 selectedTheme = overriddenScheme || preferredScheme;
 localStorage.setItem('selectedTheme', selectedTheme);

 if (selectedTheme === 'dark') {
   document.querySelector('body').classList.add('dark');
 } else {
   document.querySelector('body').classList.remove('dark');
 }
};

Then make sure we read this value on load using localStorage.getItem:

const savedTheme = localStorage.getItem('selectedTheme');
updateScheme(savedTheme);

After the above changes, you should be able to toggle the theme, refresh the page and be able to see the correct theme loaded up for you.

NOTE: Due to the way StackBlitz loads the JS, there will be a small flash, but if you would place this code in the head that flash should be extremely minimal.

What about the images

You may have noticed that we have left the image unchanged between the different themes. This was, unfortunately not by choice, as there is no native way to swap images when using class-based theming.

If you were just to implement the theme based on System Preference, you could declare different images using the media query (prefers-color-scheme: dark). This would allow you to declare images like this:

<picture>
   <source srcset="night.jpg" media="(prefers-color-scheme: dark)">
   <img src="day.jpg">
</picture>

The above code will show day.jpg if your theme settings are light or night.jpg if dark. If you would like to implement something similar with a class-based theme, you will have to create a custom image component in your framework of choice that loads the correct theme, but this is outside this tutorial's scope.

Conclusion

It is now time to conclude this post and leave you to go and apply what we learned in your Web applications. Implementing this feature is quite simple, and it helps to improve your user's experience.

The code we have written above is available on StackBlitz: Light / Dark theme functionality in Vanilla JS. But let's see it in action in:

System appearance settings Mac OS

So, in this article, we have introduced CSS variables and ensured our design colors were driven solely by them. We created an override for this variable using a class-based approach; we then learned how some elements, such as input fields, can change the theme using the "color-theme" property.

We then moved into the JS, where we used the media query "prefers-color-scheme" to read the user Operating System theme preferences and, last but not least, created a live toggle that would create and save a personal preference in LocalStorage.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

The Future of Dates in JavaScript: Introducing Temporal cover image

The Future of Dates in JavaScript: Introducing Temporal

The Future of Dates in JavaScript: Introducing Temporal What is Temporaal? Temporal is a proposal currently at stage 3 of the TC39 process. It's expected to revolutionize how we handle dates in JavaScript, which has always been a challenging aspect of the language. But what does it mean that it's at stage 3 of the process? * The specification is complete * It has been reviewed * It's unlikely to change significantly at this point Key Features of Temporal Temporal introduces a new global object with a fresh API. Here are some important things to know about Temporal: 1. All Temporal objects are immutable 2. They're represented in local calendar systems, but can be converted 3. Time values use 24-hour clocks 4. Leap seconds aren't represented Why Do We Need Temporal? The current Date object in JavaScript has several limitations: * No support for time zones other than the user's local time and UTC * Date objects can be mutated * Unpredictable behavior * No support for calendars other than Gregorian * Daylight savings time issues While some of these have workarounds, not all can be fixed with the current Date implementation. Let's see some useful examples where Temporal will improve our lives: Some Examples Creating a day without a time zone is impossible using Date, it also adds time beyond the date. Temporal introduces PlainDate to overcome this. ` But what if we want to add timezone information? Then we have ZonedDateTime for this purpose. The timezone must be added in this case, as it also allows a lot of flexibility when creating dates. ` Temporal is very useful when manipulating and displaying the dates in different time zones. ` Let's try some more things that are currently difficult or lead to unexpected behavior using the Date object. Operations like adding days or minutes can lead to inconsistent results. However, Temporal makes these operations easier and consistent. ` Another interesting feature of Temporal is the concept of Duration, which is the difference between two time points. We can use these durations, along with dates, for arithmetic operations involving dates and times. Note that Durations are serialized using the ISO 8601 duration format ` Temporal Objects We've already seen some of the objects that Temporal exposes. Here's a more comprehensive list. * Temporal * Temporal.Duration` * Temporal.Instant * Temporal.Now * Temporal.PlainDate * Temporal.PlainDateTime * Temporal.PlainMonthDay * Temporal.PlainTime * Temporal.PlainYearMonth * Temporal.ZonedDateTime Try Temporal Today If you want to test Temporal now, there's a polyfill available. You can install it using: ` Note that this doesn't install a global Temporal object as expected in the final release, but it provides most of the Temporal implementation for testing purposes. Conclusion Working with dates in JavaScript has always been a bit of a mess. Between weird quirks in the Date object, juggling time zones, and trying to do simple things like “add a day,” it’s way too easy to introduce bugs. Temporal is finally fixing that. It gives us a clear, consistent, and powerful way to work with dates and times. If you’ve ever struggled with JavaScript dates (and who hasn’t?), Temporal is definitely worth checking out....

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers cover image

“Music and code have a lot in common,” freeCodeCamp’s Jessica Wilkins on what the tech community is doing right to onboard new software engineers

Before she was a software developer at freeCodeCamp, Jessica Wilkins was a classically trained clarinetist performing across the country. Her days were filled with rehearsals, concerts, and teaching, and she hadn’t considered a tech career until the world changed in 2020. > “When the pandemic hit, most of my gigs were canceled,” she says. “I suddenly had time on my hands and an idea for a site I wanted to build.” That site, a tribute to Black musicians in classical and jazz music, turned into much more than a personal project. It opened the door to a whole new career where her creative instincts and curiosity could thrive just as much as they had in music. Now at freeCodeCamp, Jessica maintains and develops the very JavaScript curriculum that has helped her and millions of developers around the world. We spoke with Jessica about her advice for JavaScript learners, why musicians make great developers, and how inclusive communities are helping more women thrive in tech. Jessica’s Top 3 JavaScript Skill Picks for 2025 If you ask Jessica what it takes to succeed as a JavaScript developer in 2025, she won’t point you straight to the newest library or trend. Instead, she lists three skills that sound simple, but take real time to build: > “Learning how to ask questions and research when you get stuck. Learning how to read error messages. And having a strong foundation in the fundamentals” She says those skills don’t come from shortcuts or shiny tools. They come from building. > “Start with small projects and keep building,” she says. “Books like You Don’t Know JS help you understand the theory, but experience comes from writing and shipping code. You learn a lot by doing.” And don’t forget the people around you. > “Meetups and conferences are amazing,” she adds. “You’ll pick up things faster, get feedback, and make friends who are learning alongside you.” Why So Many Musicians End Up in Tech A musical past like Jessica’s isn’t unheard of in the JavaScript industry. In fact, she’s noticed a surprising number of musicians making the leap into software. > “I think it’s because music and code have a lot in common,” she says. “They both require creativity, pattern recognition, problem-solving… and you can really get into flow when you’re deep in either one.” That crossover between artistry and logic feels like home to people who’ve lived in both worlds. What the Tech Community Is Getting Right Jessica has seen both the challenges and the wins when it comes to supporting women in tech. > “There’s still a lot of toxicity in some corners,” she says. “But the communities that are doing it right—like Women Who Code, Women in Tech, and Virtual Coffee—create safe, supportive spaces to grow and share experiences.” She believes those spaces aren’t just helpful, but they’re essential. > “Having a network makes a huge difference, especially early in your career.” What’s Next for Jessica Wilkins? With a catalog of published articles, open-source projects under her belt, and a growing audience of devs following her journey, Jessica is just getting started. She’s still writing. Still mentoring. Still building. And still proving that creativity doesn’t stop at the orchestra pit—it just finds a new stage. Follow Jessica Wilkins on X and Linkedin to keep up with her work in tech, her musical roots, and whatever she’s building next. Sticker illustration by Jacob Ashley....

History of Mobile Web Development and the rise of PWA cover image

History of Mobile Web Development and the rise of PWA

If you're in your thirties, like me, or older, you know that mobile phones have not always been so ubiquitous. In this article, we are going to cover the history of the mobile phone, and more specifically "Web Development", by focusing on the rise and benefits of PWAs. The article is going to be divided in the following sections: - History of the Mobile Phone - The evolution of Mobile Web Development - The Rise of PWA (Progressive Web App) History of the Smart Phone It is hard to remember a time when we all lived without smart phones in our pockets. How about when mobile phones could not be called "smart" since they had a single purpose: to make calls? Let's travel in time, and cover the main milestones that shaped our beloved handsets. March 1991 Even if it feels like ancient history, the story of mobile phones starts just shy of the millenium's turn. Phones started to become everyday accessories in the last decade of the 20th Century, with the first ever phone call on GSM network being made on March 1991. 1996 During their first few years of existence, phones rapidly advanced. In 1996, Nokia released the first phone with access to the "mobile web". 1998 1998 did not have any specific technological breakthroughs, but in my opinion, it marks the creation of a new industry: premium downloadable content. You may be curiuous to know which app was the first to be downloaded, but here, we are not talking about any app, as the phones did not even have web browsers yet. The first content to be sold were ringtones. This is one of the most succesful (and annoying) ringtone of the early 2000s "crazy frog": {% youtube awAKzaGrrbY %} 1999 Right before the end of the decade, phones reached another very important milesone. In this year, the first version of a mobile specific web browser was created, and our handsets were connected to the World Wide Web. 2000-2007 If I would have to list all the great phones and enhancements of these few years, this blogpost would become a book. Phones started to get much smarter. There were multiple improvements, from the size of the devices, to the signal coverage, all the way up to improved services, such as colored screens and cameras. All of the above made consumers interested in the products, and more and more people started to use them. Making it a very profitable market to invest in. 2007 The fast growth of this market made Apple decide to move into this growing sector. In the year 2007 Steve Jobs release the company's first mobile device: the Iphone. The Iphone was the first mobile interface to be completely touch-based. This was a big evolution, not only in terms of user experience, but also on the web development side. 2007+ Following the Iphone, all major companies of the time, such as Blackberry, and Nokia, started to emulate the success of Apple, and consumers were inundated with different devices every year. By the end of this decade, Mobile Phones were common devices for adults and teenagers, and have since developed and improved to what we have today. The evolution of Mobile Web Development Now that we have covered the actual evolution of our beloved handsets in terms of hardware, it is time to go back in time again to analyze the evolution of "mobile web development". As shown from the above section, the "web" made its first apprearance on Mobile phones in 1999. WML - 1999 I never had a chance to develop a mobile site in the late 90s. If I had, I would have had to use WML (Wireless Markup Language). This language (now obsolete), started to provide powerful features (link, forms, image, etc..), and was based on XML. Mobile development was specific, as this language was specific to devices that implemented the WAP (Wireless Application Protocol). The use of these sites on desktop was limited, or required the use of browser extensions. cHTML / iHTML - 1999 The browser capabilities on mobile phones developed quite quickly. Devices started to have access to subsets of HTML (cHTML and iHTML). This allowed developers to be able to create one asset (HTML) and serve a multitude of users, both on mobile and desktop. M.dot Mobile phone resolution was quite low, and phones in this period did not have touchscreens, making it difficult to surf the wed. To help in this matter, the industry respondend by utilizing what was called M.Dot methodology. This allowed a website to have two different entities, on two separate Urls. For example, if your site was myShop.com, when accessed from a mobile phone, you would have redirected users to m.myShop.com. Having different websites, allowed the developer to "eliminate" surplus content, and create a design that was more suited for the hardware of the time. This "duplication" came, of course, with a big development cost price tag. Native - 2008 The App store, and the Google Play store, were release in 2008. These events marked the beginning of a completely new industry. Internet speed on phones was very slow, and the price of data usage was still quite high. This enabled many companies to specialize in Native App development. These apps provided a quick and smooth experience to users, but with a great cost factor in terms of development and specialization. The distinct programming languages, and methodology needed to create Native Apps, was so wide across different devices, that many companies decided to specialize in a specific platform (Android, Apple or Windows). Native applications had the possibility to "connect" with the device, in unique ways, by having access to a huge set of APIs (eg. Push notification, background sync, use device space, etc..) Responsive Web design - 2008 But with the rise of Native App, another methodology started to gain traction: Responsive Web Design. This approach allowed the same website to render well on a variety of devices. Using this approach made the use of M.Dot methodology obsolete, and helped many companies save thousands of dollars in Native development. Unfortunately, "web apps" built this way would just be simple websites, and would require the user to access the internet, and specifically access the site URL. Hybrid App development - 2010 Responsive web design never managed to fully respond to customer requests. This was most likely due to the look and feel of the app being somehow different from a Native app. As phones developed, the gap between Native and Web Based widened, as the latter was not able to use many of the in-built features. In 2010, PhoneGap was revealed (currently known as Apache Cordova). This sofware enabled the development of applications using known web skills (html, css, javascript) to build Native Applications. More software followed this trend, enabling companies to start in-house development of Native Apps, without the need to incur high costs. PWA - NOW The last decade has been a roller coaster, as companies were torn among the use of Native app, Hybrid solutions, and responsive designs. In recent years, a new contender, or more precisely described as an upgrade of the Web App, entered the ring. This is known as the Progressive Web App (PWA). The primary intent of PWAs is to reduce the gap between web development and Native apps, by enchancing the user experience. In the next section, we are going to cover why the industry is responsing so positively to PWAs, and provide you useful insight to help you get up to speed with this methodology. The Rise of PWA (Progressive Web App) As you may have probably noticed from the way I speak about PWAs, I am personally really excited about this methodology, and in this section, we are going to explain the reason behind my interest in this technology. Feels like "home" First and foremost, the main advantage of PWAs is the ability for our websites/web application to be downloaded as real applications. When a website is fully setup as a PWA, mobile users are prompted with a "install now" action. Doing this will allow specific content (manually set as part of the PWA setting) to be downloaded directly on the phone, by allowing extremely quick load times and more. Furthermore, installing a PWA will also add it directly to the main mobile interface (homescreen), making it feel like a Native App. Be connected If you own a business, you are fully aware that connecting with the customer is essential. Therefore, if your customers are using your services online, being able to interact, and catch their attention, is vital. One of the main benefits of Native and Hybrid apps, as previously mentioned, was the ability to access the multitude of APIs offered by our handset. Thanks to PWAs, and more specifically to the use of a service worker, we will be able to make use of this powerful feature. Two of the most important features available on Web applications are: - Sync notification: Ability to sync your data, even if the app is not running. This is essential to provide quick content to your user. - Push notification: Ability to provide an alert to the customer, even if the app is not running ( eg. New notification in a specific app, or a reminder to use the app) It is true that the list of avaialble APIs is still small compared to that of Native implementation, but the community is working extremely hard to reduce this gap. Write for Web, serve on mobile When we previously covered RWD, we highlighted one ot its biggest advantages: being able to develop one single interface to serve a multitude of devices. PWAs offer the same advantages, with the added feature of providing the ability to display fullscreen like a "real app". This feature (part of the display setting of PWA) allows your app to take the full screen. This not only increases the space available to your app, but it also enhances the User Experience, by making sure the customer is not distracted by the "url" bar, by providing a "native look and feel". Additionally, we should highlight that the front end industry has seen a big rise in the use of frameworks, such as Vue, React, and Angular. The development of applications with "component based" architectures has supported the rise of theUI component library which is: > A cloud-based folder that consists of all the designed/styled parts of a website or piece of software. It helps designers to work in a consistent way and becomes very time efficient when executed correctly. The use of these libraries (most available for free), have supported the development of nice looking applications across multiple devices, without the need to develop all individual elements from scratch. Ready for shopping In recent months, the Google Play Store has supported PWAs as shown by this article. This service, though still in its early stages of implementation, provides a reassuring signal for the future of PWAs. You may be thinking, what about the Apple Store? Unfortuantely, Apple has not disclosed any information about PWA adoptation, but recent signals, highlighted in this medium article, show Apple's support of PWA development. At a fraction of the cost Unfortuantely, when it comes to business, cost is usually the most important factor. To further emphasise the above point, where we shared the introduction and use of "component based" architecture for speed, we also need to talk about development cost. There are three main aspects of PWAs that help reduce cost: - Existing skills - Development speed - Re-usability Existing skills Apart from some tinkering, and the introduction of the service worker, PWAs are just simple websites. They would allow a multitude of in-house development teams to be able to carry out development and/or support mobile apps, reducing the need to pay third party companies. Development speed As already mentioned above, component based achitectures, and the multitude of available community based components, have provided frontend developers the ability to quickly build performant PWAs in record time. Re-usability Many companies fail to quantify the financial savings produced by being able to re-use existing features. In a recent project I was involved with, we were able to transition from a Native App (supported by a third party company), to a fully in-house build PWA in record time, all thanks to the reusability of their Vue domponents! Versioning I have read many different articles that introduced PWAw, but I have personally learned this last point though experience. Until now, you would have never associated the word "versioning" with a website, but thanks to PWAs, this ability is also possible for web based applications. The main reason behind the need for versioning is purely technical, but its existence within PWAs is surely favorable to the many "Native" developers wanting to swap. Conclusion The war for Mobile Application domination is still on, but I personally believe that PWAs will hold a big share of the market within the next few years. Most of the advantages that support Native and Hybrid development, such as APIs, Native-look and feel, and the ability to publish within the big stores, are slowly being added to Web App. Even if Native applications are not going anytime soon, as there are many use cases that require its full control of the handset API,I believe that in the next few years, PWAs will become the standard choice for most in the mobile industry....

The simplicity of deploying an MCP server on Vercel cover image

The simplicity of deploying an MCP server on Vercel

The current Model Context Protocol (MCP) spec is shifting developers toward lightweight, stateless servers that serve as tool providers for LLM agents. These MCP servers communicate over HTTP, with OAuth handled clientside. Vercel’s infrastructure makes it easy to iterate quickly and ship agentic AI tools without overhead. Example of Lightweight MCP Server Design At This Dot Labs, we built an MCP server that leverages the DocuSign Navigator API. The tools, like `get_agreements`, make a request to the DocuSign API to fetch data and then respond in an LLM-friendly way. ` Before the MCP can request anything, it needs to guide the client on how to kick off OAuth. This involves providing some MCP spec metadata API endpoints that include necessary information about where to obtain authorization tokens and what resources it can access. By understanding these details, the client can seamlessly initiate the OAuth process, ensuring secure and efficient data access. The Oauth flow begins when the user's LLM client makes a request without a valid auth token. In this case they’ll get a 401 response from our server with a WWW-Authenticate header, and then the client will leverage the metadata we exposed to discover the authorization server. Next, the OAuth flow kicks off directly with Docusign as directed by the metadata. Once the client has the token, it passes it in the Authorization header for tool requests to the API. ` This minimal set of API routes enables me to fetch Docusign Navigator data using natural language in my agent chat interface. Deployment Options I deployed this MCP server two different ways: as a Fastify backend and then by Vercel functions. Seeing how simple my Fastify MCP server was, and not really having a plan for deployment yet, I was eager to rewrite it for Vercel. The case for Vercel: * My own familiarity with Next.js API deployment * Fit for architecture * The extremely simple deployment process * Deploy previews (the eternal Vercel customer conversion feature, IMO) Previews of unfamiliar territory Did you know that the MCP spec doesn’t “just work” for use as ChatGPT tooling? Neither did I, and I had to experiment to prove out requirements that I was unfamiliar with. Part of moving fast for me was just deploying Vercel previews right out of the CLI so I could test my API as a Connector in ChatGPT. This was a great workflow for me, and invaluable for the team in code review. Stuff I’m Not Worried About Vercel’s mcp-handler package made setup effortless by abstracting away some of the complexity of implementing the MCP server. It gives you a drop-in way to define tools, setup https-streaming, and handle Oauth. By building on Vercel’s ecosystem, I can focus entirely on shipping my product without worrying about deployment, scaling, or server management. Everything just works. ` A Brief Case for MCP on Next.js Building an API without Next.js on Vercel is straightforward. Though, I’d be happy deploying this as a Next.js app, with the frontend features serving as the documentation, or the tools being a part of your website's agentic capabilities. Overall, this lowers the barrier to building any MCP you want for yourself, and I think that’s cool. Conclusion I'll avoid quoting Vercel documentation in this post. AI tooling is a critical component of this natural language UI, and we just want to ship. I declare Vercel is excellent for stateless MCP servers served over http....

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co