nuxt / vercel-builder

Vercel Builder for Nuxt
MIT License
643 stars 76 forks source link

Async Axios data to @nuxtjs/sitemap breaks when pushed to Vercel #516

Open selfagency opened 3 years ago

selfagency commented 3 years ago

Ok, so let's say you have a very rudimentary blog:

Post index Live example

<!-- pages/posts/index.vue -->

<template>
  <div v-if="posts">
    <div v-for="(post, index) in posts" :key="index">
      <nuxt-link :to="`/posts/${post.id}`">
        <h1>{{ post.title }}</h1>
      </nuxt-link>
      <div>{{ post.body }}</div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Posts',
  data() {
    return {
      posts: null,
    }
  },
  async mounted() {
    this.posts = (
      await this.$axios.get(`https://jsonplaceholder.typicode.com/posts`)
    ).data
  },
}
</script>

Individual post Live example

<!-- pages/posts/_id/index.vue -->

<template>
  <div v-if="post">
    <h1>{{ post.title }}</h1>
    <div>{{ post.body }}</div>
  </div>
</template>

<script>
export default {
  name: 'Post',
  data() {
    return {
      post: null,
    }
  },
  async mounted() {
    this.post = (
      await this.$axios.get(
        `https://jsonplaceholder.typicode.com/posts/${this.$route.params.id}`
      )
    ).data
  },
}
</script>

If you click the live examples above, you can see Axios is working properly to retrieve data from the jsonplaceholder.typicode.com URLs on the live site.

Now you want to use @nuxtjs/sitemap to generate a sitemap. Everything works great locally with every single one of the following configurations. But when you push to Vercel, no matter what approach you take, the async data always comes back blank and the sitemap won't generate.

config 1: async routes

full code

// nuxt.config.js

  sitemap: {
    hostname: 'https://testsite.com',
    path: '/sitemap',
    gzip: true,
    defaults: {
      changefreq: 'daily',
      priority: 1
    },
    async routes() {
      try {
        const routes = (await axios.get('https://jsonplaceholder.typicode.com/posts')).data

        return routes.map(p => ({
          url: `/posts/${p.id}`,
          changefreq: 'weekly',
          priority: 0.8
        }))
      } catch (err) {
        console.error(err)
      }
    }
  },

local result: ✅ vercel result: ❌

error:

ERROR  Cannot read property 'map' of null
  at joinRoutes (node_modules/@nuxtjs/sitemap/lib/cache.js:76:31)
  at AsyncCache.load [as _load] (node_modules/@nuxtjs/sitemap/lib/cache.js:19:18)
  at processTicksAndRejections (internal/process/task_queues.js:93:5)

config 2: async nuxt.config.js

full code

// nuxt.config.js

export default async function () {
  try {
    let routes = (await axios.get('https://jsonplaceholder.typicode.com/posts')).data

    routes = routes.map(p => ({
      url: `/posts/${p.id}`,
      changefreq: 'weekly',
      priority: 0.8
    }))

    return {
      [...]
      sitemap: {
        hostname: 'https://testsite.com',
        path: '/sitemap',
        gzip: true,
        defaults: {
          changefreq: 'daily',
          priority: 1
        },
        routes
      },
    [...]
    }
  } catch (err) {
    console.error(err)
  }
}

local result: ✅ vercel result: ❌

error:

 ERROR  Cannot read property 'map' of null
  at joinRoutes (node_modules/@nuxtjs/sitemap/lib/cache.js:76:31)
  at AsyncCache.load [as _load] (node_modules/@nuxtjs/sitemap/lib/cache.js:19:18)
  at processTicksAndRejections (internal/process/task_queues.js:93:5)

config 3: module w/ custom server middleware

full code

// nuxt.config.js

  modules: [
    '~modules/sitemap.js',
    '@nuxtjs/sitemap'
  ],

  sitemap: {
    hostname: 'https://testsite.com',
    path: '/sitemap',
    gzip: true,
    defaults: {
      changefreq: 'daily',
      priority: 1
    },
    routes: []
  },
// modules/sitemap.js

import axios from 'axios'

const sitemap = async () => {
  try {
    const routes = (await axios.get('https://jsonplaceholder.typicode.com/posts')).data

    return routes.map(p => ({
      url: `/posts/${p.id}`,
      changefreq: 'weekly',
      priority: 0.8
    }))
  } catch (err) {
    console.error(err)
  }
}

export default function () {
  const { nuxt } = this

  this.addServerMiddleware({
    async handler(req, res, next) {
      if (req.url === '/sitemap') {
        if (nuxt.server.options.sitemap) nuxt.server.options.sitemap.routes.push(...(await sitemap()))
      }

      next()
    }
  })
}

local result: ✅ vercel result: ❌

error:

 ERROR  Cannot read property 'map' of null
  at joinRoutes (node_modules/@nuxtjs/sitemap/lib/cache.js:76:31)
  at AsyncCache.load [as _load] (node_modules/@nuxtjs/sitemap/lib/cache.js:19:18)

config 4: generate static sitemap w/o middleware

full code

// nuxt.config.js

  sitemap: {
    hostname: 'https://testsite.com',
    path: '/sitemap',
    gzip: true,
    defaults: {
      changefreq: 'daily',
      priority: 1
    },
    generate: true,
    async routes() {
      const routes = (await axios.get('https://jsonplaceholder.typicode.com/posts')).data

      return routes.map(p => ({
        url: `/posts/${p.id}`,
        changefreq: 'weekly',
        priority: 0.8
      }))
    }
  },

local result: ✅ vercel result: ❌

error:

 ERROR  Cannot read property 'map' of null
  at joinRoutes (node_modules/@nuxtjs/sitemap/lib/cache.js:76:31)
  at AsyncCache.load [as _load] (node_modules/@nuxtjs/sitemap/lib/cache.js:19:18)
danielroe commented 3 years ago

@selfagency Thanks for this. I'll have a look.

selfagency commented 3 years ago

Ok, I just discovered that, once pushed to Vercel, no async data being fed into nuxt.config.js is working at all and that the issue is not limited to the sitemap.

selfagency commented 3 years ago

Switched from Axios to Ky and it's the same deal.

selfagency commented 3 years ago

Ok, I did something crazy: I created an initializer script that fetches my config from my API, appends it to process.env and then spawns nuxt in a child process. Again: It works locally, but once you push it to Vercel, nada.

danielroe commented 3 years ago

I haven't yet looked into this, but more generally, it's bad practice for serverless to do network requests on function startup. It will dramatically increase your cold start, and even if we identify the underlying issue here I would recommend against it.

selfagency commented 3 years ago

So there are a couple of things I noticed as I continued troubleshooting this yesterday:

The first is that @nuxtjs/sitemaps only works as server middleware when running in dev mode and only works in production when the target is static and the build method is generate. This, of course, builds a static sitemap during Nuxt's build step, and does not serve middleware, as the only way server middleware works in Nuxt is when the target is server and you build. I do not believe this is how the module is intended to function, but nonetheless, if you use build with the target as server, the sitemap middleware simply does not work at all. In the end, I wound up writing my own server middleware to generate a sitemap.

The other issue I noticed is that if you make the nuxt.config.js export into a function, once pushed to Vercel, server middleware doesn't load at all, even if you're running build to server. My other custom server middleware to generate an RSS feed wouldn't load at all until I switched my config back from a function to a standard Javascript object. Every other aspect of my nuxt.config.js worked just fine, however.

With regards to the network requests on function startup, the initial API request I had in my nuxt.config.js was only made during the build step, which then — as far as I understand — places the resolved environment variables into the generated code. Ergo, it should not have an impact on lambda startup times.

Whatever the case may be, I wound up having to a) not use functions in my nuxt.config.js and b) drop the @nuxtjs/sitemap module all together in order to get my site working properly again. These issues run counter to the documentation for both Nuxt and @nuxtjs/sitemap.

VSKut commented 3 years ago

@selfagency, I have a solution: "builds": [ { "src": "nuxt.config.js", "use": "@nuxtjs/vercel-builder", "config": { "serverFiles": [".nuxt/dist/sitemap-routes.json"] } ]

Just add .nuxt/dist/sitemap-routes.json to your vercel.json and it will be work

the94air commented 2 years ago

@VSKut Can confirm that this works here too. Thanks!