Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nuxt): enable chunk error handling by default #19086

Merged
merged 17 commits into from Mar 8, 2023
Merged

Conversation

danielroe
Copy link
Member

@danielroe danielroe commented Feb 16, 2023

πŸ”— Linked issue

#19038 (comment)
#14594
resolves #19182

❓ Type of change

  • πŸ“– Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • πŸ‘Œ Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

πŸ“š Description

This is the next step to enabling chunk error handling by default in Nuxt apps.

Progress

  • add reloadNuxtApp composable.
  • add documentation for composable and chunk error handling
  • remove experimental flag for emitting errors (and enable reload strategy by default)
  • resolve emitRouteChunkError: 'reload' redirects incorrectly with hashMode: trueΒ #19182
  • allow preserving nuxtApp state during reload (requires opt-in)
  • Add more documentation for the reload strategy in Error Handling docs section

Future enhancements

  • possibly handle chunk errors outside route navigation?
  • allow users to suppress chunk errors

πŸ“ Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@codesandbox
Copy link

codesandbox bot commented Feb 16, 2023

CodeSandbox logoCodeSandbox logoΒ  Open in CodeSandbox Web Editor | VS Code | VS Code Insiders

@danielroe danielroe marked this pull request as draft February 16, 2023 14:36
@danielroe danielroe mentioned this pull request Feb 16, 2023
@mcrapts
Copy link

mcrapts commented Feb 28, 2023

I tried it out, and I like it!

Specifically I'm trying to handle the situation where the network connection is interrupted. This especially can happen often in corporate environments with VPNs. Without any handling, interrupted connections with Nuxt 3 result in a fatal error that is non-recoverable, i.e. any subsequent clicks when the connection is restored won't do anything. This is because of chunk loading errors. The user will not see this, but simply notice that interacting with the website is impossible. This is difficult because throwing an error will result in an error, due to Nuxt not being able to dynamically load the error component. I simulate this behavior by setting the throttling to "No connection" in the network tab in Chrome developer tools.

  1. The behavior is different in dev vs production. In the above scenario the chunkError hook doesn't seem to be called in dev.
  2. The hook will trigger in the production build, but now I encounter the problem that the hook is already called during prefetching. Ideally it would be possible to discard prefetch situations, or at least add the trigger to the error context, so that errors can be handled differently for prefetching vs regular navigation.

@danielroe
Copy link
Member Author

@mcrapts Would you explain more about this:

The hook will trigger in the production build, but now I encounter the problem that the hook is already called during prefetching. Ideally it would be possible to discard prefetch situations, or at least add the trigger to the error context, so that errors can be handled differently for prefetching vs regular navigation.

The reload strategy currently does not handle prefetch errors, so I take it you mean that it would be helpful to have more information in the app:chunkError hook about the situation that caused the chunk error?

@mcrapts
Copy link

mcrapts commented Mar 7, 2023

@mcrapts Would you explain more about this:

The hook will trigger in the production build, but now I encounter the problem that the hook is already called during prefetching. Ideally it would be possible to discard prefetch situations, or at least add the trigger to the error context, so that errors can be handled differently for prefetching vs regular navigation.

The reload strategy currently does not handle prefetch errors, so I take it you mean that it would be helpful to have more information in the app:chunkError hook about the situation that caused the chunk error?

Yes, exactly. Then I can differentiate between prefetch and navigation errors. Navigation errors are more critical and I want to handle them differently.

@danielroe
Copy link
Member Author

Interesting idea. Currently we get that information this way in the reload strategy:

export default defineNuxtPlugin((nuxtApp) => {
  const router = useRouter()

  const chunkErrors = new Set()

  router.beforeEach(() => { chunkErrors.clear() })
  nuxtApp.hook('app:chunkError', ({ error }) => { chunkErrors.add(error) })

  router.onError((error, to) => {
    if (chunkErrors.has(error)) {
      // it was a navigation error
    }
  })
})

We can't really annotate the error (which is emitted from vite's preload function) without adding runtime code to do so, so that might be a future issue/PR.

@danielroe danielroe marked this pull request as ready for review March 7, 2023 14:30
@danielroe danielroe requested review from antfu, pi0 and Atinux March 7, 2023 14:30
@Plinpod
Copy link

Plinpod commented Mar 23, 2023

+1 to adding the ability to suppress chunk errors.

Currently, depending on the caching strategy you have setup, new deployments will break the app until the page cache is cleared. The reason being that the cached pages will try to call JS chunk files that no longer exist. It would be good to simply suppress these errors while the cache is cleared and rebuilt.

Does anyone know of a hacky way to do this before this feature is added?

@sondh0127
Copy link

sondh0127 commented Apr 17, 2023

Would it be possible to extract it as a vite plugin for use in vite base SPA applications @danielroe

@danielroe
Copy link
Member Author

Indeed. I've already opened a PR as the first step: vitejs/vite#12084

@edwh
Copy link
Contributor

edwh commented May 18, 2023

@danielroe Is there a rationale for only handling errors on the route?

If you're using async components then you'll hit missing chunks when deploying on something like Netlify, where the old chunks are gone.

Would the solution to that be to always reload when you hit a chunk error, along the lines of:

import { joinURL } from 'ufo'
import { defineNuxtPlugin, useRuntimeConfig } from '#app/nuxt'
import { reloadNuxtApp } from '#app/composables/chunk'

export default defineNuxtPlugin({
  name: 'nuxt:chunk-reload',
  setup (nuxtApp) {
    const config = useRuntimeConfig()

    nuxtApp.hook('app:chunkError', ({ error }) => { 
        const isHash = 'href' in to && (to.href as string).startsWith('#')
        const path = isHash ? config.app.baseURL + (to as any).href : joinURL(config.app.baseURL, to.fullPath)
        reloadNuxtApp({ path, persistState: true })
    })
  }
})

I've not tested this - I'm wondering if I'm missing something Very Bad that might happen.

Copy link
Member Author

No, I think that should be a reasonable approach πŸ‘

edwh added a commit to Freegle/iznik-nuxt3 that referenced this pull request May 18, 2023
@edwh
Copy link
Contributor

edwh commented May 18, 2023

@danielroe Thanks. I need to get the current path, which I can't get from to in the chunkError hook. I tried using useRoute so that I could access route.path, but that is returning undefined. Would you expect that to work?

import * as Sentry from '@sentry/browser'
import { useRoute } from 'vue-router'
import { defineNuxtPlugin } from '#app/nuxt'
import { reloadNuxtApp } from '#app/composables/chunk'

export default defineNuxtPlugin({
  setup(nuxtApp) {
    nuxtApp.hook('app:chunkError', ({ error }) => {
      const route = useRoute()

      Sentry.captureMessage(
        'Caught chunk error in ' +
          route?.path +
          ', will reload: ' +
          JSON.stringify(error)
      )

      reloadNuxtApp({
        path: route?.path ? '/' : route.path,
        persistState: true,
      })
    })
  },
})

@danielroe
Copy link
Member Author

danielroe commented May 18, 2023

Try getting the route outside the hook - i.e. in the body of the plugin? You can also directly use window.location.

@edwh
Copy link
Contributor

edwh commented May 19, 2023

@danielroe Thanks, I think that now works. But I think we might be able to do better, at least on Netlify. I've opened a separate issue #20950.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

emitRouteChunkError: 'reload' redirects incorrectly with hashMode: true
7 participants