Skip to content

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

Merged
danielroe merged 17 commits into
mainfrom
feat/reload
Mar 8, 2023
Merged

feat(nuxt): enable chunk error handling by default#19086
danielroe merged 17 commits into
mainfrom
feat/reload

Conversation

@danielroe

@danielroe danielroe commented Feb 16, 2023

Copy link
Copy Markdown
Member

🔗 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.

@danielroe danielroe added ✨ enhancement New feature or improvement to existing functionality 3.x 🍰 p2-nice-to-have Nice to have, not blocking labels Feb 16, 2023
@danielroe danielroe added this to the v3.3 milestone Feb 16, 2023
@danielroe danielroe self-assigned this Feb 16, 2023
@codesandbox

codesandbox Bot commented Feb 16, 2023

Copy link
Copy Markdown

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

mcrapts commented Feb 28, 2023

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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

mcrapts commented Mar 7, 2023

Copy link
Copy Markdown
Contributor

@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
Copy Markdown
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, atinux and pi0 March 7, 2023 14:30
Comment thread packages/nuxt/src/app/composables/chunk.ts
Comment thread packages/nuxt/src/app/composables/chunk.ts Outdated
@sondh0127

sondh0127 commented Apr 17, 2023

Copy link
Copy Markdown

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

@danielroe

Copy link
Copy Markdown
Member Author

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

@edwh

edwh commented May 18, 2023

Copy link
Copy Markdown
Contributor

@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.

@danielroe

Copy link
Copy Markdown
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

edwh commented May 18, 2023

Copy link
Copy Markdown
Contributor

@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

danielroe commented May 18, 2023

Copy link
Copy Markdown
Member Author

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

@edwh

edwh commented May 19, 2023

Copy link
Copy Markdown
Contributor

@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

Labels

3.x ✨ enhancement New feature or improvement to existing functionality 🍰 p2-nice-to-have Nice to have, not blocking

Projects

None yet

Development

Successfully merging this pull request may close these issues.

emitRouteChunkError: 'reload' redirects incorrectly with hashMode: true

7 participants