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.
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
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
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
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
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);
}
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" });
}
};
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;
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 ❤️
Top comments (15)
Hey @mfts this tutorial of yours uses the pages directory or the new app directory?
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
Thanks : )
This is pages directory, I believe as the way he designed api is also changed in new next js
Okay
Awesome tutorial. :)
Thanks Deb 👍
🚀
🌕
Good read, thanks for sharing.
Thanks 🙏
thanks for the tutorial, mupdf seems super handy!
Yes it‘s fantastic
Really nice explanation!
You‘re welcome