In this article, we will create a basic Next.js application that runs Web assembly (WASM) in a react client component.
If you don't know what WASM is, I will not explain it here. You can check out the MDN article here.
First, we need to create our starter app. We will use create-next-app and bun to create a new next.js app.
bunx create-next-app
What is your project named? next-wasm
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like your code inside a `src/` directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to use Turbopack for `next dev`? No
Would you like to customize the import alias (`@/*` by default)? No
After installation, you can run your app with the bun run dev command and hope everything is working fine.
To work with our code, we can delete unnecessary starter codes from the home page and keep only the basic code.
import Image from "next/image"
export default function Home() {
return (
<div className="p-4 flex w-full h-full items-center gap-[10rem] justify-center flex-col">
<div className="flex flex-row gap-[2rem] items-center justify-center">
<Image className="dark:invert" src="/next.svg" alt="Next.js logo" width={180} height={38} priority />
<div className='text-[4rem]'>/</div>
<Image src="/wasm.svg" alt="WASM logo" width={90} height={90} priority />
</div>
<main className='text-white text-2xl font-[500]'>
{/*TODO*/}
</main>
</div>
)
}
You can download wasm.svg from Wikipedia here and paste it in the public directory of our project.
Create a new file named hello.c in the src/c directory of our project. Then paste the following code into it.
#include <stdio.h>
#include <emscripten/emscripten.h>
#define EXTERN
EXTERN EMSCRIPTEN_KEEPALIVE void myFunction(int argc, char **argv)
{
printf("Hello world\n");
}
The only thing the myFunction function does is print "Hello World" on the console. EMSCRIPTEN_KEEPALIVE tells the compiler and linker to preserve a symbol, and export it. We will define EXPORTED_FUNCTIONS in the next step.
To compile our C code to WASM, we need to use emscripten. If you don't have it installed, you can install it here.
After installation, we can compile our C code to WASM by using my custom bash script. Paste it in src/bash/gen_hello_wasm.bash file.
script_dir="$(dirname "$(realpath "$0")")"
echo $script_dir
cd $script_dir
file_name="hello"
out_files_path="../../public"
in_files_path="../c"
echo "Setting up emsdk environment..."
source /home/notfound404/emsdk/emsdk_env.sh
echo "Compiling code..."
emcc -o $out_files_path/$file_name.js $in_files_path/$file_name.c \
-O3 \
-s EXPORTED_FUNCTIONS="['_myFunction']" \
-s "EXPORTED_RUNTIME_METHODS=['ccall']"
sed -i "s/Module/$file_name/g" $out_files_path/$file_name.js
All the bash script does is:
-O3 — optimization level. THIS IS IMPORTANT: without it the output JS will be 50kb+ and slow to load.-s EXPORTED_FUNCTIONS="['_myFunction']" — exported function names-s "EXPORTED_RUNTIME_METHODS=['ccall']" — exported runtime methodsIn our home page, we can load our wasm file using next.js Script component from next/script.
<main className="text-2xl font-[500] text-white">
<Script src="/hello.js" />
</main>
hello.js will setup the wasm environment and load the file content into the window object. We can then access it via window.hello.
Let's create a button to run our wasm code. Paste the following in src/app/DisplayMsgBtn.tsx.
'use client';
export const DisplayMsgBtn = () => {
const handleOnClick = () => {
if (!window.hello) return;
window.hello.ccall('myFunction', null, null, null);
alert('Check console for output');
};
return (
<button
onClick={handleOnClick}
className="cursor-pointer rounded-md bg-white px-3 py-1 text-2xl font-[500] text-black duration-300"
>
Display Message
</button>
);
};
If you are using TypeScript, you might see a Property 'hello' does not exist on type 'Window' error. Create src/types/window.d.ts:
export { }
declare global {
interface Window {
hello: any;
}
}
Add DisplayMsgBtn in your home page:
<main className="text-2xl font-[500] text-white">
<Script src="/hello.js" />
<DisplayMsgBtn />
</main>
And that's it! You can see the output in the browser console.
I hope this article helped you understand how to run WASM in nextjs. This was my first article, thank you for reading it. If you have any questions or suggestions, please feel free to reach out to me.