sanity-io / visual-editing

https://visual-editing-studio.sanity.build
MIT License
37 stars 20 forks source link

fix(next-loader): improve error handling #2107

Closed stipsan closed 2 weeks ago

stipsan commented 2 weeks ago

By default errors will be logged to the console, instead of thrown during render. With special handling for CORS origin errors (reported using console.warn):

Sanity Live is unable to connect to the Sanity API as it's not in the list over allowed origins for your project. Add it here: https://sanity.io/manage/project/hiomol4a/api?cors=add&origin=http%3A%2F%2F127.0.0.1%3A3009

The onError prop on <SanityLive />, and the new isCorsOriginError utility, can be used to customise this behaviour. Since the onError prop is eventually called by a client component, it's important that the function you give it comes from a file that has 'use client' at the top.

In other words this wouldn't work:

// src/app/layout.tsx

import {SanityLive} from '@/sanity/lib/live'

function handleError(error) {
  console.error(error)
}

export default function RootLayout({children}: {children: React.ReactNode}) {
  return (
    <html lang="en">
      <body>
        {children}
        <SanityLive onError={handleError} />
        {(await draftMode()).isEnabled && <VisualEditing />}
      </body>
    </html>
  )
}

This would work (and is what the default Next.js error message for passing a regular function might lead you to do), but is not recommended, as it would needlessly run a POST request to the server, calling your handleError which is now an inline Server Action:

// src/app/layout.tsx

import {SanityLive} from '@/sanity/lib/live'

function handleError(error) {
+ 'use server'
  console.error(error)
}

export default function RootLayout({children}: {children: React.ReactNode}) {
  return (
    <html lang="en">
      <body>
        {children}
        <SanityLive onError={handleError} />
        {(await draftMode()).isEnabled && <VisualEditing />}
      </body>
    </html>
  )
}

It's also wrong to add use client to the layout.tsx itself, as <SanityLive /> is a react server component, that renders <SanityLiveClientComponent /> internally, a client component. Thus client components are not allowed to use this and you'll get an error:

// src/app/layout.tsx
+'use client'

import {SanityLive} from '@/sanity/lib/live'

function handleError(error) {
  console.error(error)
}

export default function RootLayout({children}: {children: React.ReactNode}) {
  return (
    <html lang="en">
      <body>
        {children}
        <SanityLive onError={handleError} />
        {(await draftMode()).isEnabled && <VisualEditing />}
      </body>
    </html>
  )
}

The right approach is to create a separate file, for example client-utils.ts, here showing how to use sonner to show toasts when an error happens:

// src/app/client-utils.ts
'use client'

import {isCorsOriginError} from '@sanity/next-loader'
import {toast} from 'sonner'

export function handleError(error: unknown) {
  if (isCorsOriginError(error)) {
    const {addOriginUrl} = error
    toast.error(`Sanity Live couldn't connect`, {
      description: `Your origin is blocked by CORS policy`,
      duration: Infinity,
      action: addOriginUrl
        ? {
            label: 'Manage',
            onClick: () => window.open(addOriginUrl.toString(), '_blank'),
          }
        : undefined,
    })
  } else if (error instanceof Error) {
    console.error(error)
    toast.error(error.name, {description: error.message, duration: Infinity})
  } else {
    console.error(error)
    toast.error('Unknown error', {
      description: 'Check the console for more details',
      duration: Infinity,
    })
  }
}

In your layout.tsx you can then pass it as a regular prop without issue:

// src/app/layout.tsx

+import {Toaster} from 'sonner'
import {SanityLive} from '@/sanity/lib/live'
+import {handleError} from './client-utils'

export default function RootLayout({children}: {children: React.ReactNode}) {
  return (
    <html lang="en">
      <body>
        {children}
+      <Toaster />
        <SanityLive onError={handleError} />
        {(await draftMode()).isEnabled && <VisualEditing />}
      </body>
    </html>
  )
}

The end result is a nice toast:

image

Which takes you to Sanity Manage, much the same way Sanity Studio does when CORS errors happen there:

image
vercel[bot] commented 2 weeks ago

The latest updates on your projects. Learn more about Vercel for Git โ†—๏ธŽ

Name Status Preview Comments Updated (UTC)
live-visual-editing-next โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-astro โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-next โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-next-with-i18n โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-nuxt โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-page-builder-demo โŒ Failed (Inspect) Nov 6, 2024 5:04pm
visual-editing-remix โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-storybook โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-studio โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
visual-editing-svelte โœ… Ready (Inspect) Visit Preview ๐Ÿ’ฌ Add feedback Nov 6, 2024 5:04pm
socket-security[bot] commented 2 weeks ago

New and removed dependencies detected. Learn more about Socket for GitHub โ†—๏ธŽ

Package New capabilities Transitives Size Publisher
npm/react-dom@19.0.0-rc-66855b96-20241106 Transitive: environment +1 6.44 MB fb, gnoff, react-bot, ...2 more
npm/react@19.0.0-rc-66855b96-20241106 None 0 238 kB fb, gnoff, react-bot, ...1 more
npm/sonner@1.7.0 None 0 247 kB emilkowalski

๐Ÿšฎ Removed packages: npm/react-dom@19.0.0-rc-33c7bd9a-20241104, npm/react@19.0.0-rc-33c7bd9a-20241104

View full reportโ†—๏ธŽ