Why Use Serverless Functions in Next.js on Sitecore XM Cloud?

Enhance performance and security in your Next.js application with serverless functions

April 29, 2024

By Mike Payne

In the world of web development, efficiency, scalability, and simplicity are key goals for architects aiming to innovate. Next.js shines among the many tools available, blending React and Node.js smoothly to create powerful web apps. Yet, for enterprise-level scalability and flexibility in platforms like Sitecore XM Cloud, adding serverless functions is crucial. Here's why incorporating serverless functions into Next.js is vital for security and speeding up web development in the Sitecore and XM Cloud environment.

Scalability and Cost Efficiency

Traditional server-based architectures require provisioning and managing server instances to handle varying loads. However, with serverless functions, scalability becomes inherent. These functions automatically scale up or down based on demand, ensuring optimal performance without the need for manual intervention. As a result, developers can focus on building functionality rather than worrying about infrastructure management, ultimately reducing operational costs by paying only for actual usage.

Simplified Development and Deployment

Integrating serverless functions into Next.js projects streamlines the deployment process. With platforms like Vercel offering seamless integration, deploying serverless functions alongside your Next.js application is effortless.

To create new serverless functions, we need to add .ts files to the following folder: /pages/api/. Each file in this directory corresponds to a serverless function that can be triggering via an API route. Serverless functions can handle different types of HTTP requests, such as POST, GET, PUT, and DELETE. Below is an example of a serverless function that just returns a simple string message.

import { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
    res.status(200).json({ message: 'Hello world' });
}

Improved Performance

Serverless functions enable developers to offload computationally intensive tasks from the client-side to the server-side, resulting in improved performance and a smoother user experience. By executing functions closer to the data source or API endpoint, latency is minimized, leading to faster response times. Moreover, the ability to cache responses at the edge further enhances performance, especially for geographically distributed users.

Note cache is enabled by default but can be toggled off where required. In this snippet of code, we are calling our serverless function from a component using a fetch with the no-store flag on cache options.

fetch("/api/myfunction", {
 method: "GET",
 cache: "no-store", // disable cache
})

Enhanced Security

Serverless architectures inherently reduce the attack surface by abstracting away the underlying infrastructure. Enhanced security is a cornerstone of serverless functions within Next.js, especially when considering the critical aspect of server-side validation of properties. Server-side validation of properties involves inspecting and verifying data inputs on the server before processing them. This approach adds an extra layer of security to web applications by preventing malicious or error prone data from reaching the backend systems. We can make life easy by using tools like validator.js rather than trying to reinvent the wheel with our own regex functions.

import { NextApiRequest, NextApiResponse } from 'next';
import validator from 'validator';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { email, password } = req.body;

  // Server-side validation
   if (!email || !validator.isEmail(email)) {
    return res.status(400).send({ message: 'Invalid email in request body' });
  }

  if (!password) {
    return res.status(400).json({ error: 'Password is required.' });
  }

  // Process form submission
  // ...
}

Another benefit is hiding security related properties like access tokens, API keys, and Client Ids and Client Secrets for connecting to third party applications. The following code snippet shows how a private API token could be retrieved from our env variables on the server-side in our serverless function, thus hiding it from the client.

import { NextApiRequest, NextApiResponse } from 'next';

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const token = process.env.PRIVATE_TOKEN;

  try {
  const headers = new Headers({
     Authorization: `Bearer ${token}`,
     Accept: 'application/json, text/plain, */*',
     'Content-Type': 'application/json',
  });

  const response = await fetch(finalSubmitPath, {
    method: 'POST',
    headers: Object.fromEntries(headers),
  });

  const data = await response.json();

  return res.status(response.status).json(data);
  } catch (error) {
     const message = error instanceof Error ? error.message : 'unknown error';
       return res.status(500).json({ message: 'Error occurred: ' + message });
  }
}

Seamless Integration With Third-Party Services

Next.js applications often rely on third-party services for various functionalities such as authentication, database operations, or external APIs. Serverless functions seamlessly integrate with these services, enabling developers to create lightweight APIs or event-driven workflows. Whether it's processing payments, sending emails, or orchestrating workflows, serverless functions serve as the glue between different services, fostering a modular and extensible architecture.

The following snippet ties together some of the concepts mentioned previously when making a call to Stripe financial services.

// pages/api/processPayment.js
import { NextApiRequest, NextApiResponse } from 'next';
import validator from 'validator';
import stripe from 'stripe';

const stripeClient = stripe(process.env.STRIPE_SECRET_KEY);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { amount, currency, token } = req.body;

    // Check if amount is a valid value
    if (!amount|| !validator.isCurrency(amount)) {
    return res.status(400).send({ message: 'Invalid amount in request body' });
  }

  // Check if the string is a valid ISO 4217 officially assigned currency code.
    if (!currency|| !validator.isISO4217(currency)) {
    return res.status(400).send({ message: 'Invalid currency in request body' });
  }

  // Check if token is a JWT token
  if (!token || !validator.isJWT(token)) {
    return res.status(400).send({ message: 'Invalid token in request body' });
  }

  try {
    const paymentIntent = await stripeClient.paymentIntents.create({
      amount,
      currency,
      payment_method: token,
      confirm: true,
    });
    res.status(200).json({ success: true, paymentIntent });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'Payment processing failed' });
  }
}

Flexibility and Extensibility

Serverless functions empower developers with unparalleled flexibility and extensibility. Whether it's implementing custom business logic, orchestrating complex workflows, or integrating with emerging technologies like machine learning or Internet of Things (IoT), serverless functions provide a platform-agnostic approach. Developers can choose from a myriad of programming languages and frameworks, ensuring compatibility with existing codebases and future-proofing their applications.

Moving Forward With Serverless in Next.js

In conclusion, the integration of serverless functions within Next.js projects, especially within the context of Sitecore XM Cloud environments, represents a paradigm shift in web development. By harnessing the scalability, simplicity, and performance benefits of serverless architectures, developers can construct resilient and efficient applications that effortlessly scale with enterprise demands. In the face of rapid technological evolution, embracing serverless functions in Next.js is no longer just a choice but an essential strategy to maintain a competitive edge in the dynamic landscape of modern web development, particularly within the Sitecore XM Cloud ecosystem.



Mike Headshot

Mike Payne

Development Team Lead

Mike is a Development Team Lead who is also Sitecore 9.0 Platform Associate Developer Certified. He's a BCIS graduate from Mount Royal University and has worked with Sitecore for over seven years. He's a passionate full-stack developer that helps drive solution decisions and assist his team. Mike is big into road cycling, playing guitar, working out, and snowboarding in the winter.