How to show full screen error page with throw createError()

Hello, I'm writing a small personal blog. The page is a server-rendered dynamic page that takes a slug as input, retrieves a post if it exists from an API service, and displays the post. Since it's not interactive and I would like posts to be readable without enabling javascript, so I'm using the .server.vue suffix. Previously, I used throw createError() to display a fullscreen error (with error.vue) which works with client side rendering. But for some reason, it simply doesn't display anything but the surrounding page (using NuxtPage component in app.vue) when rendered on the server. I checked the browser console and looked over the Nuxt dev tools, doesn't appear to be anything wrong there. The dev server is displaying the errors in the terminal, but that's it. New to Vue and Nuxt, appreciate any help. Here is the setup script in question:
//~/pages/blog/[slug].server.vue
<script setup lang="ts">
import type { RawBlogPost, BlogPost } from '~/types/posts';

const config = useRuntimeConfig();
const route = useRoute();
const { data: post, pending, error, refresh} = await useAsyncData('post',
() => $fetch<RawBlogPost>(`/api/posts/${route.params.slug}`, { baseURL: config.public.apiBase }),
{
transform: (rawPost: RawBlogPost): BlogPost => {
const post = {
...rawPost,
date: new Date(rawPost.date),
last_updated: new Date(rawPost.last_updated),
}

return post;
},
}
)

function formatMonthDayYear(date: Date): string {
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'long',
day: 'numeric',
};

return new Intl.DateTimeFormat('en-US', options).format(date);
}

if (!post.value) {
throw createError({
statusCode: 404,
statusMessage: 'Post not found',
fatal: true,
});
}
if (error.value) {
throw createError({
statusCode: error.value.statusCode,
statusMessage: error.value.statusMessage,
fatal: true,
})
}
</script>
//~/pages/blog/[slug].server.vue
<script setup lang="ts">
import type { RawBlogPost, BlogPost } from '~/types/posts';

const config = useRuntimeConfig();
const route = useRoute();
const { data: post, pending, error, refresh} = await useAsyncData('post',
() => $fetch<RawBlogPost>(`/api/posts/${route.params.slug}`, { baseURL: config.public.apiBase }),
{
transform: (rawPost: RawBlogPost): BlogPost => {
const post = {
...rawPost,
date: new Date(rawPost.date),
last_updated: new Date(rawPost.last_updated),
}

return post;
},
}
)

function formatMonthDayYear(date: Date): string {
const options: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'long',
day: 'numeric',
};

return new Intl.DateTimeFormat('en-US', options).format(date);
}

if (!post.value) {
throw createError({
statusCode: 404,
statusMessage: 'Post not found',
fatal: true,
});
}
if (error.value) {
throw createError({
statusCode: error.value.statusCode,
statusMessage: error.value.statusMessage,
fatal: true,
})
}
</script>
3 Replies
kapa.ai
kapa.ai4mo ago
Hi @cassidymoen. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To show a full-screen error page in Nuxt 3 using throw createError(), your approach is generally correct. When you throw an error created with createError on the server side, Nuxt should render your custom error.vue page as a full-screen error page, provided that: 1. You have a properly configured ~/error.vue file in your project root (not in pages/). 2. Your layout (e.g., app.vue or default.vue) uses <slot /> instead of <NuxtPage /> to render content, so that error.vue can be displayed correctly (since error.vue is not a page component) Docs: error.vue, Stack Overflow: default layout not appearing in error.vue. Key points to check: - error.vue location and structure:
Ensure your error.vue is in the root of your project (not in pages/) and accepts the error prop as shown in the docs:
<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps({
error: Object as () => NuxtError
})
</script>

<template>
<div>
<h1>{{ error.statusCode }}</h1>
<NuxtLink to="/">Go back home</NuxtLink>
</div>
</template>

<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps({
error: Object as () => NuxtError
})
</script>

<template>
<div>
<h1>{{ error.statusCode }}</h1>
<NuxtLink to="/">Go back home</NuxtLink>
</div>
</template>

Docs: error.vue - Layout uses <slot />:
If your layout (e.g., default.vue or app.vue) uses <NuxtPage />, error.vue will not be rendered. You must use <slot /> instead:
<template>
<div>
<CustomHeader />
<main>
<slot />
</main>
<CustomFooter />
</div>
</template>

<template>
<div>
<CustomHeader />
<main>
<slot />
</main>
<CustomFooter />
</div>
</template>

Stack Overflow: default layout not appearing in error.vue - Throwing createError with fatal: true:
For server-rendered pages, setting fatal: true is recommended to ensure the error page is displayed, especially when using nuxt generate or static hosting UI Pro Docs Migration, UI Pro Error Component. - No output or only layout shown:
If you see only the layout and not the error page, it's almost always because <NuxtPage /> is used instead of <slot /> in your layout. Summary:
Update your layout to use <slot /> instead of <NuxtPage />. This will allow Nuxt to render error.vue as a full-screen error page when you throw createError() on the server, as intended. If you have already done this and still see issues, double-check that your error.vue is in the correct location and that you are not catching the error elsewhere in your code. --- References:
- Nuxt error.vue documentation - Stack Overflow: default layout not appearing in error.vue - UI Pro Error Component ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai4mo ago
<@403763587182624769> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
cassidymoen
cassidymoenOP4mo ago
Another note: Added a v-if directive that shows a "Post not found" message if the requested post slug isn't found, and that seems to work on the dev server. But in the production build, (npm run build && node .output/server/index.mjs) the space in the layout is still just blank. Chalking it up to either me missing something or server components still being experimental and janky maybe

Did you find this page helpful?