javascriptnode.jsreactjstypescriptnext.js

Getting error, Can't resolve child_process when tried to inject NPM package As a Plugin on A Next.js App


I have experience developing nuxt apps but I am new to next.js apps. On the next.js app that I am trying to make, I'm having trouble getting 'google-auth-library' to work in a component. This is my code for the component in question:

components/Middle.tsx

import React from 'react';
import { Button } from 'react-bootstrap';
import { GoogleAuth } from 'google-auth-library';

const url = "https://us-central1-gtwitone.cloudfunctions.net/<functionname>";
const keyFilename = "/path/to/my/key.json";
//const functionsClient = CloudFunctionsServiceClient.v1.CloudFunctionsServiceClient;

class Middle extends React.Component {
    async main() {
      const auth = new GoogleAuth({keyFilename: keyFilename})

      //Create your client with an Identity token.
      const client = await auth.getIdTokenClient(url);
      const res = await client.request({url});
      console.log(res.data);
  }

    render() {
      return (
        <div className="col-md-12 text-center">
            <Button variant='primary' onClick={this.main}>
                Click me
            </Button>
        </div>
      );
    }
  }

export default Middle;

I know that the code in main() works in a regular node app. I made a node app where I put that function in an index.js file and ran it with node index.js and it ran perfectly. The problem is when I run this app with yarn dev I get a blank browser page and this in my terminal:

<myusername>@<mycomputername> <myappname> % yarn dev
yarn run v1.22.17
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
wait  - compiling...
event - compiled client and server successfully in 214 ms (124 modules)
wait  - compiling / (client and server)...
wait  - compiling...
error - ./node_modules/google-auth-library/build/src/auth/googleauth.js:17:0
Module not found: Can't resolve 'child_process'

Import trace for requested module:
./node_modules/google-auth-library/build/src/index.js
./components/Middle.tsx
./pages/index.tsx

https://nextjs.org/docs/messages/module-not-found
Native Node.js APIs are not supported in the Edge Runtime. Found `child_process` imported.

Could not find files for / in .next/build-manifest.json
Could not find files for / in .next/build-manifest.json
^C
<myusername>@<mycomputername> <myappname> % 

I believe that this error is due to a problem with server side rendering. I wrote a nuxt app where I had a similar issue, with a package called 'unity-webgl'. I fixed the problem by injecting it into the app via a plugins folder. I made this file:

plugins/plug-unity-webgl.client.js

import UnityWebgl from "unity-webgl";

export default ({ _ }, inject) => {
  const Unity = new UnityWebgl({
    loaderUrl: "/Build/tetris.loader.js",
    dataUrl: "/Build/tetris.data",
    frameworkUrl: "/Build/tetris.framework.js",
    codeUrl: "/Build/tetris.wasm",
  });
  inject("webgl", Unity);
};

I also needed to declare this in nuxt.config.js

import colors from 'vuetify/es5/util/colors'

export default {
  // Set server port
  server: {
    port: 8080
  },
  /*
  ** Nuxt rendering mode
  ** See https://nuxtjs.org/api/configuration-mode
  */
  mode: 'universal',

  // Target: https://go.nuxtjs.dev/config-target
  target: 'static',

  // Global page headers: https://go.nuxtjs.dev/config-head
  head: {
    titleTemplate: '%s - appFrontend',
    title: 'appFrontend',
    htmlAttrs: {
      lang: 'en'
    },
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' },
      { name: 'format-detection', content: 'telephone=no' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },

  // Global CSS: https://go.nuxtjs.dev/config-css
  css: [
  ],

  // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
  plugins: [
    '~/plugins/persistState.js',
    '~/plugins/axios.js',
    '~/plugins/plug-unity-webgl.client.js'
  ],

  // Auto import components: https://go.nuxtjs.dev/config-components
  components: true,

  // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
  buildModules: [
    // https://go.nuxtjs.dev/vuetify
    '@nuxtjs/vuetify'
  ],

  // Modules: https://go.nuxtjs.dev/config-modules
  modules: [
    // https://go.nuxtjs.dev/axios
    '@nuxtjs/axios'
  ],

  // Axios module configuration: https://go.nuxtjs.dev/config-axios
  axios: {
    // Workaround to avoid enforcing hard-coded localhost:3000: https://github.com/nuxt-community/axios-module/issues/308
    browserBaseURL: process.env.API_URL
  },

  // Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
  vuetify: {
    customVariables: ['~/assets/variables.scss'],
    theme: {
      dark: true,
      themes: {
        dark: {
          primary: colors.blue.darken2,
          accent: colors.grey.darken3,
          secondary: colors.amber.darken3,
          info: colors.teal.lighten1,
          warning: colors.amber.base,
          error: colors.deepOrange.accent4,
          success: colors.green.accent3
        }
      }
    }
  },

  // Build Configuration: https://go.nuxtjs.dev/config-build
  build: {
  }
}

Note the plugins arrary. So how would I do something similar with this next.js app I'm working on? The next.config.js file looks way different than the nuxt.config.js file did:

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
}

module.exports = nextConfig

Can you inject plugins to get them to run client side only with a next.js app? How would I do this?


Solution

  • They key is that you have to put the GoogleAuth api call in the pages/api route. This is an example of how to call a Google Cloud Function. Btw, I made the basic template for the example with the command npx create-next-app@latest call-to-cloud-exp --typescript

    pages/api/google.ts

    // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
    import type { NextApiRequest, NextApiResponse } from "next"
    import { GoogleAuth } from "google-auth-library"
    
    export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
        const url = process.env.FUNCTION_URL as string
    
        //Example with the key file, not recommended on GCP environment.
        const auth = new GoogleAuth({ keyFilename: process.env.KEYSTORE_PATH })
    
        //Create your client with an Identity token.
        const client = await auth.getIdTokenClient(url)
        const result = await client.request({ url })
        console.log(result.data)
        res.json({ data: result.data })
    }
    

    components/Middle.tsx

    import React from "react"
    import { Button } from "react-bootstrap"
    
    class Middle extends React.Component {
      handleClick() {
            console.log("this is:", this)
        }
      
      // this talks with /pages/api/google
      async imCallingAnAPI() {
        const result = await fetch("/api/google")
        console.log({ result })
      }
    
      render() {
        return (
          <div className="col-md-12 text-center">
            <Button variant="primary" onClick={this.imCallingAnAPI}>
              Click me
            </Button>
          </div>
        )
      }
    }
    
    export default Middle
    

    pages/index.tsx

    import type { NextPage } from 'next'
    import Header from '../components/Header';
    import Footer from '../components/Footer';
    import Middle from '../components/Middle';
    
    const Home: NextPage = () => {
      return (
        <><main className='d-flex flex-column min-vh-100'>
          <Header />
          <br></br>
          <br></br>
          <Middle />
          </main>
          <footer>
            <Footer />
          </footer>
        </>
      )
    }
    
    export default Home