I wanted to create a public web interface for my semi-public journal. I want to do server side rendering so that new content can be shown without rebuilding the site. Inspired from Sam Selikoff’s video From Gatsby to Next.js: The Power of Incremental Static Regeneration(opens new window).

I chose Nuxt because:


Setting up Nuxt with SSR and API on Vercel: Vercel has a Nuxt template but it is SPA mode, not SSR (server-side rendering). Setting up SSR is pretty straightforward, but when it comes to deployment I hit a few roadblocks. I assumed this would take 10 minutes but I ended up spent over an hour trying to figure this out.

As a result I sent the PR #373 to improve the docs(opens new window) (outdated):

Original PR #373 contents (outdated)

A common use case in Nuxt is to use serverMiddleware(opens new window) put an API server on the same server as Nuxt. This requires some set up:

  1. Set up serverMiddleware config in nuxt.config.js:

      serverMiddleware: [
        { path: '/api', handler: '~/api/index.js' },
      ],
    

    Note: This assumes that your API will be served at /api and its source code is in api/index.js (same as Nuxt’s documentation(opens new window)).

  2. Go to your Project SettingsEnvironment Variables and add an environment variable VERCEL_URL to all environments. When you type in the name, Vercel will say that it is a system environment variable(opens new window) and its value will be automatically populated by the system.

  3. Set up serverFiles in now.json:

    {
      "version": 2,
      "builds": [
        {
          "src": "nuxt.config.js",
          "use": "@nuxtjs/vercel-builder",
          "config": {
            "serverFiles": ["api/**"]
          }
        }
      ]
    }
    
  4. Set up @nuxtjs/axios to use the base URL from Vercel.

    const baseUrl =
      process.env.baseUrl ||
      (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : undefined)
    export default {
      // ...
      modules: [['@nuxtjs/axios', withoutNullishEntries({ baseURL: baseUrl })]],
      env: withoutNullishEntries({
        baseUrl,
      }),
    }
    function withoutNullishEntries(x) {
      return Object.fromEntries(Object.entries(x).filter(([k, v]) => v != null))
    }
    

I later spent some more time fiddling with the builder and submitted PR #375(opens new window) which makes this use case "just work" on Vercel. It has been merged.


Authentication: Setting up Auth0 + Nuxt(opens new window) was easy. By following the documentation, I got to the point where I can display the user’s info in Nuxt webpage. However the docs does not say anything about what to do afterwards. Same goes for all the tutorials I looked at.

You can now log in and log out! You can see your own name!! Congratulations!!! Now your app has a authentication system!!!!

What now? How do I protect my API endpoints to allow only authenticated users??

After one more hour of trial-and-error, I figured it out: I need to set up 2 separate applications in Auth0.

  1. Set up a web application and put the Client ID in @nuxtjs/auth config.

  2. Set up an API (machine-to-machine) and put the Identifier as audience in @nuxtjs/auth config. Use RS256.

  3. The Auth module will automatically configure Axios to send the Authorization: header with a proper JWT.

  4. In API, use express-jwt and jwks-rsa to authenticate the incoming request. Thankfully, Auth0’s API dashboard has a Quick Start tab with code you can copy and use with Express app right away.

Handling invalid and expired JWTs in express-jwt: Normally, if JWT is not provided the middleware throws an error. This can be fixed by adding credentialsRequired: false in express-jwt options.

However, soon I start getting 500 errors due to expired JWT. There are no settings to prevent this (issue)(opens new window) but a workaround is to put an “on error resume next” handler for invalid_token case:

app.use(jwtCheck, function (err, req, res, next) {
  if (err.code === 'invalid_token') return next()
  return next(err)
})

After this, use req.user to check for presence of a valid JWT token.