sanity-io / hydrogen-sanity

A Sanity toolkit for Hydrogen
MIT License
60 stars 12 forks source link

Issue with Presentation mode in production #86

Closed javangriff closed 3 weeks ago

javangriff commented 3 months ago

Running into some issues with presentation mode in production with Shopify Oxygen. Locally presentation mode in working without issue, however, when trying to view the site in presentation mode via the Sanity Studio using the production domain I get an error in the console "Unable to connect to visual editing. Make sure you've setup '@sanity/visual-editing' correctly".

This seems to be an issue with how the Sanity Studio connects to and displays the Hydrogen storefront within the studio iframe, as when visiting the preview endpoint and passing the preview token via the sanity-preview-secret query param, it displays the presentation mode correctly within the browser and I am able to click through from there to the studio.

Which versions of Sanity are you using?

@sanity/cli (global) 3.49.0 (latest: 3.50.0) @sanity/asset-utils 1.3.0 (up to date) @sanity/color-input 3.1.1 (up to date) @sanity/eslint-config-studio 4.0.0 (up to date) @sanity/icons 2.11.8 (latest: 3.3.0) @sanity/orderable-document-list 1.2.1 (up to date) @sanity/table 1.1.2 (up to date) @sanity/ui 2.6.8 (up to date) @sanity/vision 3.49.0 (latest: 3.50.0) sanity 3.50.0 (up to date)

Edit: Hydrogen version is 2024.7.1 and hydrogen-sanity version is 4.0.5

javangriff commented 3 months ago

I was able to fix this by overriding the resource.preview.tsx route, choosing to include the code provided by this package within hydrogen-sanity/preview/route rather than importing as per the docs. Doing this meant I could override the Set-Cookie header as per a previous issue #27.

// app/routes/resource.preview.tsx

[...]

const cookie = await context.session.commit();

return redirect(redirectTo, {
    headers: {
      'Set-Cookie': cookie.replace('SameSite=Lax', 'SameSite=None; Secure'),
    },
});

Not sure if there is a better way this can be handled by this package to maintain the ability to import the loader and action from hydrogen-sanity/preview/route so that any changes to these functions can be maintained with future updates.

sanschaise commented 3 months ago

@javangriff We are having similar issues but we didn't fully understand your solution? Would you be able to show us more completely what your route now look like?

javangriff commented 3 months ago

@sanschaise it should look something like this:

import {validatePreviewUrl} from '@sanity/preview-url-secret'
import {type ActionFunction, json, type LoaderFunction, redirect} from '@shopify/remix-oxygen'

/**
 * A `POST` request to this route will exit preview mode
 */
export const action: ActionFunction = async ({context, request}) => {
  if (request.method !== 'POST') {
    return json({message: 'Method not allowed'}, 405)
  }

  const cookie = await context.session.commit()

  return redirect(redirectTo, {
      headers: {
        'Set-Cookie': cookie.replace('SameSite=Lax', 'SameSite=None; Secure'),
      },
  })
}

/**
 * A `GET` request to this route will enter preview mode
 */
export const loader: LoaderFunction = async ({context, request}) => {
  const {sanity} = context
  const projectId = sanity.client.config().projectId

  if (!sanity.preview?.token || !projectId) {
    throw new Response('Unable to enable preview mode. Please check your preview configuration', {
      status: 500,
    })
  }

  const clientWithToken = sanity.client.withConfig({
    useCdn: false,
    token: sanity.preview.token,
  })

  const {isValid, redirectTo = '/'} = await validatePreviewUrl(clientWithToken, request.url)

  if (!isValid) {
    throw new Response('Invalid secret', {status: 401})
  }

  const cookie = await context.session.commit()

  return redirect(redirectTo, {
      headers: {
        'Set-Cookie': cookie.replace('SameSite=Lax', 'SameSite=None; Secure'),
      },
  })
}
nkgentile commented 3 months ago

Hey @javangriff, thanks for reporting and providing a workaround.

I wanted to just add that hydrogen-sanity is using the session that is passed to your request context. For example, in one of the examples it's initialized with these options:

  static async init(request: Request, secrets: string[]) {
    const storage = createCookieSessionStorage({
      cookie: {
        name: 'session',
        httpOnly: true,
        path: '/',
        sameSite: 'lax',
        secrets,
      },
    });

    // ...rest of the constructor
  }

Adjusting the loader to suit your needs is absolutely a valid option, but I wonder if it's also possible to adjust or conditionally set these options in your session implementation?

In other examples I've seen, like this one, there are two sessions–one for the normal visitor and another for preview. I can look into providing more configuration in hydrogen-sanity to support this setup, if you think it would be helpful!

madblokus commented 1 month ago

@nkgentile I'd love to have the simplest repository where both of these features work out of the box:

currently I can't get neither of them to work. Sanity live feature can't connect like in this thread

and the deployment fails when we finally use sanity to fetch the data on some route/inititate the client the deployment to oxygen fails

ramiroazar commented 1 month ago

We're having the same issue.

I tried customising the loader (ie. cookie.replace('SameSite=Lax', 'SameSite=None; Secure')) as suggested by @javangriff.

I also tried customising the session (ie. sameSite: "none", secure: true) as suggested by @nkgentile.

We're open to having two separate sessions, but given the above didn't work, there's no point at this stage.

As mentioned, opening the iframe src directly in the browser works as expected.

It also works with the preview route (ie. /resource/preview) running in the local development server instead of the staging/production domain.

This happens with both the studio running locally and deployed to Sanity.

Our application is deployed to Cloudflare Pages instead of Oxygen, not sure how relevant this is given they're both worker runtimes and everything else besides the presentation tool is working well.

It definitely still seems related to the session/cookie, domains and/or iframe, but not sure why the above suggestions didn't resolve this in our case.

ramiroazar commented 1 month ago

Actually, that was my bad, just configuring the session as suggested by @nkgentile does indeed work.

I've also created separate sessions like the example and everything seems to be working locally and in production, thank you.

dominikx96 commented 2 weeks ago

Thanks @javangriff for the fix, now I am able to load my storefront properly. I have another issue, because when I load my storefront in presentation, I don't see any documents attached to the opened page, but locally everything works properly. Any ideas?

javangriff commented 2 weeks ago

@dominikx96 No worries! By documents attached, do you mean documents within a reference field?

dominikx96 commented 2 weeks ago

I mean all documents on the opened page, on localhost I see the list of documents, but after deployment the list is empty

Screenshot 2024-10-22 at 10 27 35

javangriff commented 2 weeks ago

Oh of course. Are you seeing any other indications that Visual Editing is enabled? Highlights around text etc.

dominikx96 commented 2 weeks ago

@javangriff I only see indications on localhost, but not after deploying to oxygen. I used your code from above and created a preview route. Any chance to see your full project config or important files in terms of sanity and preview +/or session?

I don't know where should I start to debug this. Do you have any additional config while fetching data (loadQuery)?

That is weird because it works on localhost, but on oxygen - not. I don't see any errors in console.

javangriff commented 1 week ago

@dominikx96 sounds like it may be something to do with the configuration of your createSanityLoader do you have any environment specific settings in there? I didn't have to add any additional config with loadQuery.