nuxt: 404 errors don't work on nuxt generate

Environment


  • Operating System: Darwin
  • Node Version: v16.20.0
  • Nuxt Version: 3.6.1
  • Nitro Version: 2.5.2
  • Package Manager: npm@8.19.4
  • Builder: vite
  • User Config: -
  • Runtime Modules: -
  • Build Modules: -

Reproduction

https://staging--coolplanet.netlify.app/404

Describe the bug

We are using Nuxt generate on Netlify to generate pre-rendered routes. however, when doing so, our 404 error page doesn’t render. I can confirm locally in dev mode, the 404 mode works perfectly.

I am using throw createError({ statusCode: 404 }); to trigger 404s when our fetch calls dont work.

this set up works fine, without pre-rendering enabled.

Additional context

This is my nuxt.config

import { resolve } from "path";
import sanityClient from "@sanity/client";

import dynamicRoutes from "./helpers/dynamicRoutes";

const client = sanityClient({
  projectId: process.env.SANITY_PROJECT_ID,
  apiVersion: process.env.SANITY_API_VERSION,
  useCdn: process.env.SANITY_CDN,
  dataset: process.env.SANITY_DATASET,
});

export const getRedirects = async () => {
  let redirectsObject = {};

  const redirectQuery = `*[_type == "redirect"]{
		'from':fromPath,
		'redirect':{'to':toPath, 'statusCode':statusCode}
	}`;
  const data = await client.fetch(redirectQuery);
  if (data && data.length) {
    data.forEach((item) => {
      redirectsObject[item.from] = {
        redirect: item.redirect,
      };
    });
    console.info("Redirects are generated");
  } else {
    console.info("No redirects sourced");
  }
  return redirectsObject;
};

export default defineNuxtConfig({
  app: {
    head: {
      title: "Cool Planet",
      htmlAttrs: {
        lang: "en-GB",
      },
      meta: [
        { name: "viewport", content: "width=device-width, initial-scale=1" },
        { charset: "utf-8" },
      ],
      link: [
        {
          rel: "apple-touch-icon",
          sizes: "180x180",
          href: "/favicon/apple-touch-icon.png",
        },
        {
          rel: "icon",
          type: "image/png",
          sizes: "32x32",
          href: "/favicon/favicon-32x32.png",
        },
        {
          rel: "icon",
          type: "image/png",
          sizes: "16x16",
          href: "/favicon/favicon-16x16.png",
        },
        { rel: "manifest", href: "/favicon/site.webmanifest" },
      ],
      script:[
        {id:"youtube-iframe-js-api-script", 
        src:"https://www.youtube.com/iframe_api"}
      ]
    },
  },
  modules: [
    "@nuxtjs/sanity",
    "nuxt-jsonld",
    "@pinia/nuxt",
    "@nuxt/image-edge",
    "@nuxtjs/robots",
    "nuxt-simple-sitemap",
    'nuxt-schema-org'
  ],
  routeRules: getRedirects(),
  hooks: {
    "pages:extend"(pages) {
      pages.push(
        {
          name: "site.blogIndex",
          path: "/blog",
          file: resolve("/pages/blog/page/[page].vue"),
        },
        {
          name: "site.caseStudiesIndex",
          path: "/case-studies",
          file: resolve("/pages/case-studies/page/[page].vue"),
        },
        {
          name: "site.articlesIndex",
          path: "/blog/articles",
          file: resolve("/pages/blog/articles/page/[page].vue"),
        },
        {
          name: "site.featuresIndex",
          path: "/blog/featured",
          file: resolve("/pages/blog/featured/page/[page].vue"),
        },
        {
          name: "site.reportsIndex",
          path: "/blog/reports",
          file: resolve("/pages/blog/reports/page/[page].vue"),
        },
        {
          name: "site.masterclassesIndex",
          path: "/blog/masterclasses",
          file: resolve("/pages/blog/masterclasses/page/[page].vue"),
        }
      );
    },
  },
  robots: () => {
    if (process.env.ENVIRONMENT && process.env.ENVIRONMENT === "production") {
      // production environment - allow robots
      return {
        sitemap: process.env.BASE_URL + "/sitemap.xml",
        UserAgent: "*",
        Disallow: ["/admin", "/.env", "/users"],
      };
    } else {
      // every other environment - block robots
      return {
        sitemap: process.env.BASE_URL + "/sitemap.xml",
        UserAgent: "*",
        Disallow: "/",
      };
    }
  },
  sitemap:{
    exclude:[
      '/styles',
      '/kitchen-sink'
    ]
  },
  sanity: {
    projectId: process.env.SANITY_PROJECT_ID,
    apiVersion: process.env.SANITY_API_VERSION,
    useCdn: process.env.SANITY_CDN,
    dataset: process.env.SANITY_DATASET,
  },
  runtimeConfig: { 
    public:{
      siteName:'Cool Planet',
      siteUrl:process.env.BASE_URL,
      baseUrl:process.env.BASE_URL,
      gtmId:process.env.GTM_ID
    },
    },
  target: "static",
  image: {
    // Options
    sanity: {
      projectId: "say5yn59",
      dataset: "production",
    },
    screens: {
      xxs: 320,
      xs: 359,
      sm: 640,
      md: 768,
      lg: 1024,
      xl: 1280,
      xxl: 1536,
      xxxl: 1920,
      quadHd: 2400,
      "4k": 3700,
    },
  },
  css: ["@/assets/scss/main.scss"],
  vite: {
    css: {
      preprocessorOptions: {
        scss: {
          additionalData: '@use "@/assets/scss/tools.scss" as *;',
        },
      },
    },
  },
  alias: {
    "@app": "/@app",
  },
  nitro: { // was required to add this for the nuxt/simple-sitemap module
    prerender: {
      crawlLinks: true,
      routes: [
        '/',
        '/404.html' // was told online to try this, but didn't work
      ]
    }
  }
});

Logs

No response

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 55 (8 by maintainers)

Most upvoted comments

Let’s track in https://github.com/nuxt/nuxt/issues/18718.

@annymosse If you want a Nuxt site with zero JS, it’s as simple as:

export default defineNuxtConfig({
  experimental: {
    noScripts: true,
  }
})

Hi @annymosse ,

It does solve the static pages if you can prerender them. In case of storyblok, I can get all stories and then prerender then in the nitro hook

Ignore target static - that was a mistake. Did you try the nitro pretender settings?

  nitro: {
    prerender: {
      crawlLinks: true,
      routes: [
        '/',
        '/404.html'
      ]
    }
  }

Could it be the fact you are using pnpm? I have not switched to this yet, but can confirm it works with classic npm.

Otherwise it might be to do with how your throwing your 404 errors – code here, as it looks like your 404s are being thrown before the API query is complete. Did you try using nuxt’s standard lazyAsyncData instead, in combination with checking for the pending listener as suggested here?

Hi, I had a similar issue with Sanity’s wrapper for Async Data, which looks similar to StoryBloks. So I ditched that and just used Nuxt’s default useAsyncData, in combination with Sanity fetch.

const sanity = useSanity(); // I expect StoryBlok has a similar fetch module to this

const {pending,data: entry,error } = await useLazyAsyncData(route.path, () => sanity.fetch(myQuery)); // using sanity's fetch call but within the standard Nuxt AsyncData function

if (error?.value) {
  throw createError({ statusCode: 500, fatal:true });
} else if (!error?.value && !pending?.value && !entry?.value) { // listening for the pending variable allows me to use lazyAsyncData
    throw createError({ statusCode: 404, fatal:true }); // IMPORTANT - Make sure you add fatal:true
} else if (entry?.value) {
  // Handle successful fetch
}

@stijns96 I use Storyblok and ran into some issues too with that, this is what I got it to work in my […slug].vue

let story;

try {
  story = await useAsyncStoryblok(
    route && route.path !== "/" ? route.path : "home",
    {
      version: runtimeConfig.public.currentEnv,
      resolve_relations: resolveRelations,
    }
  );

  story.value = story.value;
} catch (error) {
  throw showError({
    statusCode: 404,
    statusMessage: "Page Not Found",
    fatal: true,
  });
}

@toddpadwick can confirm the issue was using pnpm generate rather than pnpm build.

Resolved my issues right away!

Hi all. So heres the detailed break down of how I fixed this:

Do not use nuxt generate. Instead use nuxt build. Initially I thought Nuxt Generate was required for static generation but its not. I have Nuxt set up to run npm run build but to ensure the site is pre-crawled and generated on build, in my nuxt.config I have the following added:

target: "static", 
generate: {
    fallback: true
  },
  nitro: {
    prerender: {
      crawlLinks: true,
      routes: [
        '/',
        '/404.html'
      ]
    }
  }

Ensure 400 and 500 errors are fatal Now, I went back and forth with Daniel on this and initial it seemed I could not use lazyAsyncData as the errors were being thrown before the fetch call was complete. But I managed to get it working by doing the following:

const sanity = useSanity(); // not important - just my fetch call function.
const {
  pending,
  data: entry,
  error,
} = await useLazyAsyncData(route.path, () => sanity.fetch(query));

if (error?.value) {
  throw createError({ statusCode: 500, fatal:true });
} else if (!error?.value && !pending?.value && !entry?.value) { // listening for the pending variable allows me to use lazyAsyncData
    throw createError({ statusCode: 404, fatal:true }); // IMPORTANT - Make sure you add fatal:true
} else if (entry?.value) {
  // Handle successful fetch
}

I can confirm this successfully pre-crawls and generates the entire site and 404s work just fine on netlify. Hope this helps 😃

oh sorry, i’ll email you. found you email on the profile 😃

@toddpadwick Happy to help but really we’d need more info - sharing the code would be perfect. Feel free to DM if there’s info you can’t share in this issue.