Use WASM Compiled Golang Functions in NextJs

24th Nov 2022

Use WASM Compiled Golang Functions in NextJs

What if you are using Next.js for your website but want to make use of Golang functionality written to support your backend APIs? This is a quick look at the Tinygo WebAssembly guide and how I integrated the output with NextJs.

The Tinygo Guide

Here is the Tinygo guide I used. It gives you a very basic first step to building a wasm binary from Go. Getting Golang set up correctly on my system was the hardest part for me, but even that didn't take too long.

Once you have followed the guide you end up with:

  • a wasm binary
  • a main.go file with a basic multiply function exposed
  • wasm_exec.js copied to your project (I used the Nextjs public folder)

Loading the WASM inside Nextjs Code

The guide gives a snippet of code to load the wasm file. This is the area I would really like to find a cleaner solution for. With Nextjs out-of-the-box, there is no support for importing a wasm file like import x from 'my.wasm'. Without this, it is much harder to distribute Node packages with wasm dependencies.

Webpack itself gives a couple of options, I could use the file-loader or enable the experimental wasm support. This is a little painful with the knowledge that Nextjs are phasing out Webpack from the build system and so I am concerned that using these features would make it harder to migrate to newer versions or make use of updated features.

Ok, that's something to come back to another day. For the purpose of this exercise, I have the wasm file in the public folder so it is possible to fetch it directly. I implemented this in two parts.

A useWasm hook (which is less DRY in this example than it sounds) and the corresponding usage at the top of the Home page React component.

Home:

export default function Home() {
const mod = useWasm('/golang-nextjs.wasm')
console.log('wasm', mod)

useWasm Hook:

function useWasm(path: string) {
const [state, setState] = useState<[any, boolean, Error | null]>([null, true, null])
useEffect(() => {
async function getWasm(path: string) {
try {
// @ts-ignore
const go = new Go(); // Defined in wasm_exec.js
go.importObject.env = {
'add': function(x: number, y: number) {
return x + y
}
}
const WASM_URL = path;
var wasm;
if ('instantiateStreaming' in WebAssembly) {
const obj = await WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject)
wasm = obj.instance;
go.run(wasm);
return wasm.exports
} else {
const resp = await fetch(WASM_URL)
const bytes = await resp.arrayBuffer()
const obj = await WebAssembly.instantiate(bytes, go.importObject)
wasm = obj.instance;
go.run(wasm);
return wasm.exports
}
} catch (e) {
console.log(e);
return {}
}
}
getWasm(path)
.then((exp) => {
setState([exp, false, null])
})
.catch((err) => {
setState([null, false, err])
})
}, [path])
return state
}

Thoughts and next steps

This was a very simple piece of Go code which worked easily but there wasn't many Go dependencies involved. I did try to build another example using an external dependency but quickly came up against brick walls.

I want to investigate the import wasm story further with Next, SWC and Turbopack. They must be thinking about this already.

Get in touch if you have any experience with this and have ideas!