This Small Corner

Eric Koyanagi's tech blog. At least it's free!

Nuxt 3 useFetch not working on Page Reload

By Eric Koyanagi
Posted on

Why Can't I Load Data with useFetch in Nuxt?

This is apparently a common issue when working with Nuxt based on my Google searches. In my case, I needed to fetch data with an API for a given page, which was very effortless when navigating the application with the router. However, when I try to right click a link to open it in a new tab? It fails. The "naive" code works something like this:

const { data, error, pending, refresh } = await useFetch(api.show('recipes', currentRoute._value.params.id));

When called in a <script setup> function, this works without any issues...so long as you stay within the app navigation and don't try to load a page from the browser. Obviously, that isn't good enough.

I'm sure there's a good technical under-the-hood reason why this doesn't work as you'd expect, but personally I'd label this behavior as a "bug", or least a behavior that deserves more documentation.

It's easy to see this problem in action when you have hot reloading enabled. When I navigate directly to the URL in question where I need to fetch data, it presents errors. In my case, it surfaces a 500 error from the API itself, although that's not especially helpful because we know our API works. As soon as I change the code, hot reloading kicks in and renders the page without issue. Same code, different result. Never a great developer experience!

Fixing the Issue (Without Disabling SSR)

First, ignore advice that tells you to just disable SSR or implement other workarounds that lead to the same effect. Yes, there is something wonky with Nuxt's SSR feature...or at least working in a very poorly documented way, but the solution isn't to just disable server side rendering! Similarly, some have suggested using "nextTick" to force a delay, but (afaik) that effectively just disables SSR, too.

The whole point in working with Nuxt is that we want the server side rendering process to actually work. The fix requires a few simple steps.

  1. Move the useFetch call into onBeforeMount
  2. Makes refs for the above variables
  3. Update your view code to use "obj.value" instead of just "obj"
  4. This is because the way it "unwraps" proxy objects on the view layer can be flawed
  5. E.g. it attempts to convert an object to a string

The updated example is more verbose as a result:

const data = ref(false);
const error = ref(null);
const pending = ref(true);
const refresh = ref(null);

async function fetchData() {
  const result = await useFetch(api.show('recipes', currentRoute._value.params.id), {immediate: true});
  data.value = result.data;
  error.value = result.error;
  pending.value = result.pending;
  refresh.value = result.refresh;
}

onBeforeMount(() => {
  fetchData();
});

Simple enough, right? You probably don't need the "immediate : true" flag, as it should default as such. Pay close attention to the view -- you'll likely need to update your conditional rendering like this:

<span v-if="data && data.value">

This part is important, because the data object will exist before the data is loaded since it is a proxy object. Though the view is supposed to unwrap the data variable, you can't really rely on it to do so as of today.

Similarly, you'll need to explicitly unwrap the variable by accessing it with "data.value" instead of just "data" like you might be used to with other "refs". We can't just use the "reactive()" method, here, either since we need to reassign the object based on the API call.

In other words, I can access the data like this:

<div class="text-lg text-cyan-800">{{ data.value.recipe.name }}</div>

Again, in a perfect world you wouldn't need to explicitly use "value" because it is supposed to "unwrap" the variable for you...but this world is far from perfect.

Conclusion

If you have issues making an API work with Nuxt on direct page navigation, I hope that this article helps with a fix. I'm sure there's details I'm missing, here, but I look forward to learning more about Nuxt and its unique lifecycle and hope that this at least helps someone struggling through some basic feature development.

« Back to Article List
Written By
Eric Koyanagi

I've been a software engineer for over 15 years, working in both startups and established companies in a range of industries from manufacturing to adtech to e-commerce. Although I love making software, I also enjoy playing video games (especially with my husband) and writing articles.

Article Home | My Portfolio | My LinkedIn
© All Rights Reserved