vercel / next.js

The React Framework
https://nextjs.org
MIT License
126.84k stars 26.96k forks source link

Escaping problem with meta tags #2006

Closed Balamir closed 4 years ago

Balamir commented 7 years ago

Hi,

I'm facing an issue with meta tags. When I use some special characters like single quote in the head tag, it's escaping. I can not use dangerouslySetInnerHtml for meta tags.

in code: <meta name="description" content="That's it!" />

result: <meta name="description" content="That&#x27;s it!" />

How can I handle with this? I couldn't find any solution about this problem.

Thanks.

arunoda commented 7 years ago

@Balamir could you give us some code for this? Where we can work on locally.

Please re-open this issue after that.

Balamir commented 7 years ago

@arunoda thanks for the reply.

I just tried this with your head-elements example.

Simply I added above meta:

import Head from 'next/head'

export default () => (
  <div>
    <Head>
      <title>This page has a title 🤔</title>
      <meta charSet='utf-8' />
      <meta name="description" content="That's it!" />
      <meta name='viewport' content='initial-scale=1.0, width=device-width' />
    </Head>

    <h1>This page has a title 🤔</h1>
  </div>
)

and the result:

snip20170519_2

Thanks.

arunoda commented 7 years ago

Thanks. I'll see what's happening here.

Balamir commented 7 years ago

@arunoda I couldn't reopen this issue. It's still closed. Can you re-open this issue please?

Thanks.

Rupeshn73 commented 7 years ago

This Issue still exist .For single quote it is rendering as &#x27;

clothing,men's shoes,men's accessories,women's clothing,women's shoes,jewelry,women's handbags,women's accessories,dogs,furniture,home and decor,kitchen and dining

meta

damusnet commented 7 years ago

The same is true for urls.

Adding <meta property="og:image" content="http://example.com?foo=bar&bar=baz" /> results in <meta property="og:image" content="http://example.com?foo=bar&amp;bar=baz" class="next-head"/>.

Notice the encoded &amp;. Any way around that?

poohitan commented 6 years ago

I'm facing the same issue. Are there any updates on it?

timneutkens commented 6 years ago

I'm expecting this is an upstream React SSR issue, since we don't escape values afaik.

donaminos commented 6 years ago

Running through the same issue. any workaround please ?

stefl commented 6 years ago

I've made a workaround by post-processing the generated HTML and using a regex for the attributes that are incorrectly encoded with HTML entities:

https://gist.github.com/stefl/1f8c246dd7ca9cb332ae41f68e80088d

Obviously not a long-term fix but it means that my Opengraph metadata is now not affected by this. Hope this is of use to others here!

oliviertassinari commented 6 years ago

Please track https://github.com/facebook/react/issues/13838. I'm using the following workaround:

import Entities from 'html-entities/lib/html5-entities'

const entities = new Entities()
const escapeRegExp = /(content|href|src|srcSet)="([^"]+)"/g
const escapeHandle = (match, attribute, value) => {
  return `${attribute}="${entities.decode(value)}"`
}

// ...

html = html.replace(escapeRegExp, escapeHandle)
mrasoahaingo commented 5 years ago

Still have the same issue...

timneutkens commented 5 years ago

@mrasoahaingo it's not really relevant to post "same issue", as @oliviertassinari said it's an upstream issue. If the issue is open you can consider it's still there an not solved, so saying "same issue" is not really useful, if you want to "+1" an issue use GitHub's reaction system on the initial post to 👍

Thanks.

equinusocio commented 5 years ago

@timneutkens After 2 years it's normal to see people saying that the issue still there.

timneutkens commented 5 years ago

The last comment before the "same issue" clearly said it's a bug in React, the issue on React's side is still open, hence why it's not valuable to say something along the lines of "Same issue...", it doesn't add value, this is why the emoji reactions exist in GitHub.

equinusocio commented 5 years ago

React Helmet has a encodeSpecialCharacters boolean prop to disable the encode server side. Can it be done on next-head too?

timneutkens commented 5 years ago

Seems like Helmet completely bypasses React, which doesn't seem like a good idea imo.

timneutkens commented 4 years ago

Closing this as it's tracked on React: https://github.com/facebook/react/issues/13838

ghost commented 4 years ago

We are migration the application to next.js we have been using helmet and I tried to migrated to next/head but I found the error with for example the http://schema.org data for example to use

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

next/head convert it to, but with react-helmet return the expect value

<script type="application/ld+json">
  &quot;@context&quot;: &quot;http://schema.org&quot;,
  &quot;@type&quot;: &quot;SportsEvent&quot;,
</script>
DesignMonkey commented 4 years ago

@vxcamiloxv Have you tried with making it a string?

Like:

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

Can't remember, but think I made that work once :)

ghost commented 4 years ago

@DesignMonkey yes, my final solution was <script ype="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(json) }} />

DesignMonkey commented 4 years ago

@vxcamiloxv Oh yea sure... Maybe that was the solution I ended up with as well :)

dzinemon commented 4 years ago

@vxcamiloxv Thank you for sharing your solution! I have used it to get the proper behaviour:

const ldJSON = `<JSON goes here>` 
....
and then 
in return (<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: ldJSON }}/>)
YoannBuzenet commented 3 years ago

@oliviertassinari Where do you put this code ? I understand it's a react problem, but where do you parse the meta tag on Next.js ? If I do it on my page, I still have the escaped text in my head.

Example :

  const escapeRegExp = /(content|href|src|srcSet)="([^"]+)"/g;
  const escapeHandle = (match, attribute, value) => {
    return `${attribute}="${decode(value)}"`;
  };
  const metaTagEscaped = "default-src * 'self' data: 'unsafe-inline' 'unsafe-eval' *; child-src * 'self' data: 'unsafe-inline' 'unsafe-eval' *; script-src 'unsafe-inline' 'self' https://js.stripe.com/v3".replace(
    escapeRegExp,
    escapeHandle
  );
<Head>
        <title>{translatedPageTitle}</title>
        <meta
          http-equiv="Content-Security-Policy"
          content={metaTagEscaped}
        ></meta>
      </Head>

This doesn't work and still gives me escaped characters.

@dzinemon so you write a meta tag this ay ? I don't get how to implement it.

jasonfarrell commented 3 years ago

Found a workaround for &amp; in the image url in next.js — the main issue was the imgix params in the URL weren't sticking, due to the escaped ampersands. So I took the properties out of the meta tag, and wrote a backend proxy to deliver the image data directly:

So in your template:

const shareImage = myfile.jpg // without any query params

<meta property="og:image" content={`/api/share?image=${shareImage}`} />

Then in api/share.js:

import request from 'request'

export default async function share(req, res) {
  const { image } = req.query
  if (image) {
    request
      .get(image + '?fm=jpg&q=80&w=1402&h=738&fit=crop&crop=faces,edges')
      .pipe(res)
  } else {
    res.status(404).json({})
  }
}

Probably need to validate the incoming url, otherwise it could be open for malice, but this fixes the issue until this is merged in: https://github.com/facebook/react/issues/13838

equinusocio commented 3 years ago

Found a workaround for &amp; in the image url in next.js — the main issue was the imgix params in the URL weren't sticking, due to the escaped ampersands. So I took the properties out of the meta tag, and wrote a backend proxy to deliver the image data directly:

So in your template:

const shareImage = myfile.jpg // without any query params


<meta property="og:image" content={`/api/share?image=${shareImage}`} />

Then in api/share.js:


import request from 'request'

export default async function share(req, res) {

  const { image } = req.query

  if (image) {

    request

      .get(image + '?fm=jpg&q=80&w=1402&h=738&fit=crop&crop=faces,edges')

      .pipe(res)

  } else {

    res.status(404).json({})

  }

}

Probably need to validate the incoming url, otherwise it could be open for malice, but this fixes the issue until this is merged in: https://github.com/facebook/react/issues/13838

Thanks. Unfortunately i don't think that issue on Fb will merged. It is 3yrs old.

mislavmiocevic commented 2 years ago

I used the Next.js _document file to replace all the &amp; occurrences. I am using some code from @stefl gist to do the replacement. The main point is to use _document to handle this issue, so please ignore the code.

My use case were images with w and h query parameters being incorrectly used by the crawlers since those pages were made with getStaticProps and they all had &amp; instead of &.

// _document.tsx

import { decode } from 'html-entities';
const srcRegex = /src="([^"]+)"/g;
const srcSetRegex = /srcSet="([^"]+)"/g;

const decodeHtmlEntities = (html: string): string =>
    html
        .replace(srcRegex, (match: string, content: string) => {
            return `src="${decode(content)}"`;
        })
        .replace(srcSetRegex, (match: string, content: string) => {
            return `srcSet="${decode(content)}"`;
        });

class MyDocument extends Document {
    static async getInitialProps(ctx) {
        const initialProps = await Document.getInitialProps(ctx);
        const html = decodeHtmlEntities(initialProps.html);

        return { ...initialProps, html };
    }

    ...
// encoded.tsx page

const Page = () => {
    const imageUrl = 'https://makelight-prismic-images.imgix.net/7f97d951970bbb15a8b3744e4c69499ffe969d66_img_1010.jpg';
    const defaultImageParameters = 'w=1024&h=768';
    const largeImageParameters = 'w=1920&h=1080';
    const src = `${imageUrl}?${defaultImageParameters}`;

    console.log(src);

    return <img alt="Some image" src={src} srcSet={`${src} 1024w, ${imageUrl}?${largeImageParameters} 1920w`} />;
};

export default Page;

P.S. you can move decodeHtmlEntities in an util file decodeHtmlEntities.ts, e.g.:

import { decode } from 'html-entities';

const decodeHtmlEntitiesRegex = /(content|href|src|srcSet)="([^"]+)"/g;

export const decodeHtmlEntities = (html: string): string =>
    html.replace(
        decodeHtmlEntitiesRegex,
        (match: string, attribute: string, value: string) => `${attribute}="${decode(value)}"`
    );

See the images attached. First one is showing before the replace, and second one with the fix. Third one is to show also srcSet. There are also Node and Chrome logs showing how they didn't have issue anyway.

Before fix After fix srcSet

@timneutkens is using _document to do this replace okay?

Kudos to @isBatak for finding this out.

github-actions[bot] commented 2 years ago

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.