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

Hydration issue when rendering dynamic head element #1035

Closed
msyyn opened this issue Apr 15, 2021 · 14 comments
Closed

Hydration issue when rendering dynamic head element #1035

msyyn opened this issue Apr 15, 2021 · 14 comments

Comments

@msyyn
Copy link

msyyn commented Apr 15, 2021

Describe the bug
When using the @sveltejs/adapter-static and appending a script tag with application/ld+json type to svelte:head, it won't show up in the build version of your site/app. It shows up correctly when you're developing, but npm run build loses it from head tag.

Code sample:

<script>
const item_title = 'Hello world!';

const schema = {
    "@context": "http://schema.org",
    "@type": "Article",
    "@name": item_title
  };
</script>

<svelte:head>
  {@html '<script type="application/ld+json">' + JSON.stringify(schema) + '</script>'}
</svelte:head>

To Reproduce
Append script tag with application/ld+json attribute on svelte:head and build/export site using static adapter.

Expected behavior
Added script tag / ld json schema should be visible in head tag after exporting the site as static.

Information about your SvelteKit Installation:

Diagnostics

System:
OS: Windows 10 10.0.18363
CPU: (4) x64 Intel(R) Core(TM) i5-4690K CPU @ 3.50GHz
Memory: 2.39 GB / 7.95 GB

Binaries:
Node: 15.12.0 - C:\Program Files\nodejs\node.EXE
npm: 7.6.3 - C:\Program Files\nodejs\npm.CMD

Browsers:
Chrome: 89.0.4389.114
Edge: Spartan (44.18362.449.0)
Internet Explorer: 11.0.18362.1

npmPackages:
@sveltejs/kit: next => 1.0.0-next.74
svelte: ^3.29.0 => 3.37.0
vite: ^2.1.0 => 2.1.5

Static adapter.

Severity
Mediumish

@msyyn
Copy link
Author

msyyn commented Apr 15, 2021

After more testing, looks like the application/ld+json is indeed in place if you navigate to your desired page via on-site navigation links.

Let's assume we have site structure like this:
index.svelte
blog/[slug].svelte

And our user path is this:

  • Land on front page
  • Navigate to /blog/slug by using on-site navigation
  • application/ld+json tag is in place

Now if you refresh the page here or come with a direct link (e.g. yoursite.com/blog/article-slug) the application/ld+json will not be in place and has disappeared.

If you navigate back to front page and then again to blog/article-slug, the application/ld+json won't be in place anymore either.

So it seems to work only once per session when landing to a page that has application/ld+json, as long as you navigate to the page manually and not with a direct link. 🤔

@hobbes7878
Copy link

You may need to preserve ld+json tags in svelte-preprocess in your config:

// svelte.config.cjs
const sveltePreprocess = require('svelte-preprocess');

module.exports = {
  preprocess: sveltePreprocess({
    preserve: ['ld+json'],
    // ...
  }),
  // ...
};

@msyyn
Copy link
Author

msyyn commented Apr 15, 2021

Thanks @hobbes7878

Tried this after your suggestion and it does not fix the issue :/

@benmccann
Copy link
Member

benmccann commented Apr 15, 2021

You need to do the preserve option. I have a site where it works. I just put the contents of the script directly in <svelte:head> instead of wrapping in @html in my example though

@msyyn
Copy link
Author

msyyn commented Apr 15, 2021

Hmm. The HTML option enables to put dynamic content there. Something like this wouldn't work without it:

<script>
  const schema = {
      "@context": "http://schema.org",
      "@type": "Article",
      "@name": item.title,
      "datePublished": new Date(item.date_gmt).toISOString(),
      "dateModified": new Date(item.modified_gmt).toISOString(),
      "image": item.featured_image,
      "articleBody": item.content.rendered
    }
</script>

<!-- shows up in HTML code exactly as written in head tag which is not expected behaviour -->
<svelte:head>
  <script type="application/ld+json">
     {schemaObject}
  </script>
</svelte:head>

As you can't reference to the variables within the script tag. The HTML method was suggested here for Sapper which presumably works in it: sveltejs/svelte#2438 (comment)

Could you double confirm support for that HTML method indeed dropped in SvelteKit? I am asking because the method pointed in sveltejs/svelte#2438 (comment) works while you're developing with npm run dev, but as soon as you build the site with npm run build (static adapter) there will be issues as pointed in my earlier comment

@msyyn
Copy link
Author

msyyn commented Apr 15, 2021

I verified that this is static adapter related issue. When running and deploying app with vercel adapter there is no issues with application/ld+json not showing up after build (even if using the @html method). ping @benmccann

@benmccann
Copy link
Member

Hmm. I have a pre-rendered SvelteKit site using ld+json deployed to http://c3.ventures/ and it works just fine. My schema is static instead of dynamic so I didn't have a need for the @html. If @html works elsewhere I don't know of anything that would make it break in SvelteKit. That was just the only thing I noticed different between your example and what I'm doing

I noticed in the example you posted that you have two closing braces for schema. I wonder if that might be causing trouble?

@msyyn
Copy link
Author

msyyn commented Apr 15, 2021

Hey, the typo was only in the example - not my actual code. Fixed the original message so it doesn't confuse anyone.

The issue seems to only happen with static adapter and while using dynamic content combined with @html

Did again some further testing and I've noticed that sometimes it works and sometimes not.

Notes:

  • Coming with direct link does not usually load ld+json to head
  • Navigating via on-app links most-likely loads ld+json to head
  • Refreshing page sometimes loses ld+json from head

@benmccann benmccann reopened this Apr 15, 2021
@benmccann benmccann changed the title Static Site <script type="application/ld+json"> in <svelte:head> disappears Possible race condition prerendering dynamic head element Apr 15, 2021
@jeremyswann
Copy link

Following along with this discussion, the issue being dynamic data in <svelte:head />, the only form that seems to work as of @sveltejs/kit@1.0.0-next.115" is the @html injection method. After many tests in development and build it appears to be consistently working.

<svelte:head>
	{@html '<script type="application/ld+json">' + JSON.stringify(schemaObject)}
</svelte:head>

@kvn-shn
Copy link
Contributor

kvn-shn commented Jun 26, 2021

Just chiming in as I'm facing similar issues. As a recap, in my understanding the current state seems to be:

This is the most intuitive approach:

<svelte:head>
  <script type="application/ld+json">
     { "@context": "http://schema.org", ... }
  </script>
</svelte:head>

To make it work, preprocess({ preserve: ['ld+json'] }) has to be set in svelte.config.js. (Might be addressed in sveltejs/svelte-preprocess#305 ?!)

Unfortunately there is a huge downside: the schema object cannot be created dynamically. To address this problem, there is this workaround:

<svelte:head>
    {@html `<script type="application/ld+json">
        ${ JSON.stringify({ '@context': 'https://schema.org', ...schema }) }
    </script>`}
</svelte:head>

Now TypeScript is giving me pretty weird errors for the whole file (see screenshot) and Svelte Prettier is doing strange stuff too (converting the script tag to something like [...]tion/ld+json" ✂prettier:content✂="JHsgSlNPTi5zdHJp[...]).
svelte-dynamic-head-element-ts-issue

Though, someone on Stack Overflow found another workaround to keep TypeScript and Prettier happy:

<svelte:head>
    {@html `<script type="application/ld+json">${schema + "<"}/script>` }
</svelte:head>

This seems to do the trick, but looks and feels rather hacky.

BUT, again there is an issue: Now the JSON-LD script tag is included twice in the header. Even worse, the initial one won't go away when navigating your website. That means

  • e.g. Google will parse two identical JSON-LDs (I verified it via their testing tool)
  • Search engines might get confused as an incorrect/outdated JSON-LD will be served while browsing the website.
  • I suspect these issues will do the opposite and even affect SEO negatively

Can someone verify that the dynamic tag is included twice (and one of them doesn't update)? What can we do to solve this?

@jeremyswann
Copy link

Hey @kvn-shn, did you try my example above, I'm running both typescript and sveltest prettier plugin and it's all working for me. Initially, I tried something similar to you on my first attempt. I think the issue is using variables in a string literal inside a compiled injector.

@benmccann benmccann changed the title Possible race condition prerendering dynamic head element Hydration issue when rendering dynamic head element Jun 28, 2021
@benmccann
Copy link
Member

I investigated this a bit and filed an issue for it over in the Svelte core repo sveltejs/svelte#6463. I'm closing this bug in favor of that one

@kvn-shn
Copy link
Contributor

kvn-shn commented Jun 28, 2021

Hey @kvn-shn, did you try my example above

Yes, I did. But for me (using Firefox) the missing closing script tag caused the script to "eat up" other tags SvelteKit was adding below. So I didn't investigate further.

@jer-0
Copy link

jer-0 commented Jul 30, 2021

A work around for this for anyone using google structured data, is to include it the body and not the head, since google doesn't mind: https://www.youtube.com/watch?v=lI6EtxjoyDU&t=94s

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

No branches or pull requests

6 participants