DEV Community

Cover image for Deploy a WebAssembly-powered Next.js app on Vercel serverless functions
Marc Seitz
Marc Seitz

Posted on

Deploy a WebAssembly-powered Next.js app on Vercel serverless functions

What you will find in this article?

Javascript is great but sometimes you need to run some code that is not directly available in Javascript. In this article, we will see how to run WebAssembly in serverless Node.js functions using Next.js and Vercel.

thanks JS

Papermark - the open-source DocSend alternative.

Before we begin, let me introduce you to Papermark. It's an open-source alternative to DocSend that helps you manage secure document sharing, including real-time analytics, custom domains and more. It's all open-source!

I would be absolutely thrilled if you could give us a star! Don't forget to share your thoughts in the comments section ❤️
https://github.com/mfts/papermark

Papermark App

Setup the project

Let's go ahead and set up our project environment for our page number counting application. We'll be creating a Next.js app, and set up to run a WebAssembly package in a serverless function.

Setting up Next.js with TypeScript and Tailwindcss

We'll use create-next-app to generate a new Next.js project. We'll also be using TypeScript and Tailwind CSS, so make sure to select those options when prompted.

npx create-next-app

# ---
# you'll be asked the following prompts
What is your project named?  my-app
Would you like to add TypeScript with this project?  Y/N
# select `Y` for typescript
Would you like to use ESLint with this project?  Y/N
# select `Y` for ESLint
Would you like to use Tailwind CSS with this project? Y/N
# select `Y` for Tailwind CSS
Would you like to use the `src/ directory` with this project? Y/N
# select `N` for `src/` directory
What import alias would you like configured? `@/*`
# enter `@/*` for import alias
Enter fullscreen mode Exit fullscreen mode

Install vercel/blob

Vercel Storage released a new package called @vercel/blob that lets you upload files to Vercel's file storage. We'll use this package to store our PDF files.

npm install @vercel/blob
Enter fullscreen mode Exit fullscreen mode

Install mupdf

MuPDF is a lightweight PDF, XPS, and E-book viewer, but it comes with a wasm package that lets you manipulate and annotate PDFs. We'll use this package to count the number of pages in a PDF.

npm install mupdf
Enter fullscreen mode Exit fullscreen mode

Building the application

Now that we have our setup in place, we are ready to start building our application. The main features we'll cover are:

  • Upload a file
  • Count total number of pages with WASM package
  • Make WASM run on Vercel's serverless functions

#1 Upload a file

Let's create a serverless function that uploads a file to Vercel's file storage. Send a POST request to /api/upload with a file key in FormData object in the request body.

// pages/api/upload.ts
import { put } from "@vercel/blob";
import { NextResponse, NextRequest } from "next/server";

export const config = {
  runtime: "edge",
};

export default async function upload(request: NextRequest) {
  const form = await request.formData();
  const file = form.get("file") as File;

  if (!file) {
    return NextResponse.json(
      { error: "File name or file not submitted" },
      { status: 400 }
    );
  }

  const blob = await put(file.name, file, { access: "public" });

  return NextResponse.json(blob);
}
Enter fullscreen mode Exit fullscreen mode

blob contains the data about our file including url which we'll use to access the file later.

Caveat: this serverless implementation only works with files smaller than 4MB. If you need to upload larger files, you'll want to use a client-side upload (see more on GitHub).

#2 Count total number of pages with WASM package

Now that we have our file uploaded, we can use the mupdf package to count the total number of pages in the PDF. We'll create a serverless function that takes the url from blob object of the file and returns the total number of pages.

Find the full documentation of the mupdf package on their website.

// pages/api/count-pages.ts
import { NextApiRequest, NextApiResponse } from "next";
// @ts-ignore
import mupdf from "mupdf";

export default async (req: NextApiRequest, res: NextApiResponse) => {
  // check if it's a POST method
  if (req.method !== "POST") {
    res.status(405).json({ error: "Method Not Allowed" });
    return;
  }

  try {
    const { url } = req.body as { url: string };
    // Fetch the PDF data
    const response = await fetch(url);
    // Convert the response to an ArrayBuffer
    const pdfData = await response.arrayBuffer();
    // Create a MuPDF instance
    var doc = mupdf.Document.openDocument(pdfData, "application/pdf");
    // Get the number of pages
    var n = doc.countPages();

    // Send the images as a response
    res.status(200).json({ numPages: n });
  } catch (error) {
    console.error("Error:", error);
    res.status(500).json({ error: "Internal Server Error" });
  }
};
Enter fullscreen mode Exit fullscreen mode

This function will work fine locally, however, it will fail when deployed to Vercel. This is because the wasm file from mupdf node_modules package is not included in the deployment bundle. We'll fix this in the next step.

#3 Make WASM run on Vercel's serverless functions

To make the wasm file available to Vercel's serverless functions, we'll need to include the wasm file from the package's node_modules folder in our deployment. We can do this by adding the following to our next.config.js file.

In this case we are using the experimental.outputFileTracingIncludes option to include the wasm file from mupdf package in our deployment to the API route /api/count-pages.

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    outputFileTracingIncludes: {
      "/api/count-pages": ["./node_modules/mupdf/lib/*.wasm"],
    },
  },
};

module.exports = nextConfig;
Enter fullscreen mode Exit fullscreen mode

That's it. Now we can deploy our application to Vercel and it will work as expected.

Conclusion

And there you have it! We've built a simple app for counting numbers of pages in a PDF document using Next.js, WebAssembly and Vercel serverless functions. While the example here is simple, the same concepts can be applied to handle any other WebAssembly package that expands the capabilities of your app.

Thank you for reading. I am Marc, an open-source advocate. I am building papermark.io - the open-source alternative to DocSend.

Keep experimenting and pushing the boundaries of what's possible with these technologies. Happy coding!

Help me out!

If you found this article helpful and got to understand WebAssembly on Vercel serverless functions a bit better, I would be super grateful if you could give us a star! And don't forget to share your thoughts in the comments ❤️

https://github.com/mfts/papermark

cat thanks

Top comments (15)

Collapse
 
srbhr profile image
Saurabh Rai

Hey @mfts this tutorial of yours uses the pages directory or the new app directory?

// pages/api/count-pages.ts
import { NextApiRequest, NextApiResponse } from "next";
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mfts profile image
Marc Seitz

I’m using the pages router. But even if you use the app router you can colocate the pages api route alongside your app router.

It might actually not be much more effort to change it to app router

Collapse
 
srbhr profile image
Saurabh Rai

Thanks : )

Collapse
 
ayush4345 profile image
Ayush

This is pages directory, I believe as the way he designed api is also changed in new next js

Collapse
 
srbhr profile image
Saurabh Rai

Okay

Collapse
 
debadyuti profile image
Deb

Awesome tutorial. :)

Collapse
 
mfts profile image
Marc Seitz

Thanks Deb 👍

Collapse
 
shnai0 profile image
Iuliia Shnai

🚀

Collapse
 
mfts profile image
Marc Seitz

🌕

Collapse
 
uliyahoo profile image
uliyahoo

Good read, thanks for sharing.

Collapse
 
mfts profile image
Marc Seitz

Thanks 🙏

Collapse
 
matijasos profile image
Matija Sosic

thanks for the tutorial, mupdf seems super handy!

Collapse
 
mfts profile image
Marc Seitz

Yes it‘s fantastic

Collapse
 
fernandezbaptiste profile image
Bap

Really nice explanation!

Collapse
 
mfts profile image
Marc Seitz

You‘re welcome