[SalesForce] Salesforce Lightning and meta tags for Facebook / SEO

I have been trying to create a Lightning component for Social Media that can dynamically change things like the title, description, image, etc. meta tag for Facebook. The purpose would be to populate the share popup with a desired image, description, etc that pertains to the record being displayed via a Lightning component. I'll go through my failed attempts to date.

It appears that even the adding of meta tags on the community page fails to be recognized by Facebook. This wouldn't make it dynamic, but at least it would be specific to the page.

enter image description here

You can add meta tags to the Head MarkUp that are recognized by Facebook, but this doesn't help us with making it dynamic.

enter image description here

The next thing I tried, was injecting the meta tags using into the head from my component before the sdk.js was run.

<ltng:require beforeLoadingResources="{!c.beforeInit}"
              scripts="{!join(',', $Resource.social + '/social/sdk.js')}"
              afterScriptsLoaded="{!c.doInit}" />

In the controller:

beforeInit : function(component, event, helper) {
    var m1 = document.createElement('meta');
    m1.setAttribute("property", "og:image");
    m1.setAttribute("content", "https://logo.png");
    document.head.insertBefore(m1, document.head.firstChild);
    var m2 = document.createElement('meta');
    m2.setAttribute("property", "og:description");
    m2.setAttribute("content", "this is test text for the description.");
    document.head.insertBefore(m2, document.head.firstChild);
    var m3 = document.createElement('meta');
    m3.setAttribute("property", "og:title");
    m3.setAttribute("content", "this is test Title");
    document.head.insertBefore(m3, document.head.firstChild);
    var m4 = document.createElement('meta');
    m4.setAttribute("property", "og:type");
    m4.setAttribute("content", "article");
    document.head.insertBefore(m4, document.head.firstChild);
    var m5 = document.createElement('meta');
    m5.setAttribute("property", "og:site_name");
    m5.setAttribute("content", "Test Name");
    document.head.insertBefore(m5, document.head.firstChild);
    var m6 = document.createElement('meta');
    m6.setAttribute("property", "og:app_id");
    m6.setAttribute("content", "1234");
    document.head.insertBefore(m6, document.head.firstChild);
},
doInit : function(component, event, helper) {
  FB.init({
    appId            : '1234',
    autoLogAppEvents : true,
    xfbml            : true,
    version          : 'v2.11'
  });
  FB.AppEvents.logPageView();   

  (function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s); js.id = id;
    js.src = $A.get('$Resource.social') + '/social/sdk.js';
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'facebook-jssdk'));
}

This created the meta tags as desired, and I thought it would have worked due to their being created before running the sdk, but they were not recognized by Facebook. I would still get the following error when I would scrape the page with Facebook's debugger:

The 'og:image' property should be explicitly provided, even if a value can be inferred from other tags.

I asked about this on Facebook's developer help and got this response:

Hi Logan,

Thanks for getting in touch. Meta tags, and other information in the head of a document is read before any Javascript is executed on the page. This is not exclusive to Facebook, this is generally how content is crawled on the web. Head information needs to be published by the server, not by dynamic scripts within the browser.

I hope this clarifies things,

Tim

So, the next thing I tried, was to create a VF page that I could iFrame on my component.

<iframe aura:id="vfFrame" src="{!'https://' + v.vfHost + '/socialButtons?id=' + v.recID}"/>

With this, I passed the record ID from the page hosting the iFrame, to my socialButtons VF page. In the controller for that page, I would then get the record ID with my apex controller, and could create my meta tags dynamically. The problem was that I couldn't get it to link to the iFrame hosting page, while using the meta tags from the VF page.

//this gets the meta tags I want,
//but it doesn't link to the page I want.
<div class="fb-like" 
     data-layout="standard" 
     data-action="like" 
     data-size="small" 
     data-show-faces="true" 
     data-share="true">
</div>

//This links to the appropriate page, 
//but also gathers the meta tags from that page.
<div class="fb-like" 
     data-href="{!thePageURL}" 
     data-layout="standard" 
     data-action="like" 
     data-size="small" 
     data-show-faces="true" 
     data-share="true"
</div>

Unfortunately, Facebook has changed the ability to use the URL/Link to inject the title, image, description, etc due to their attempt to prevent those that were 'abusing it'. I don't know if anyone else has had any success in being able to have a Facebook share button that uses the information in a dynamic fashion on a Lightning Community Page. I'm running out of ideas…

Best Answer

Okay, I have been working on this for a few weeks, and the only solution I came up with in order to make the content dynamic, is to put the lightning components on a VF page, and to make it dynamic that way.

First thing you have to do is create a lightning App that references all of the lightning components that you want to show on your page:

<aura:application access="GLOBAL" extends="ltng:outApp">
    <c:MenuCmp />
    <c:BodyCmp />
    <c:FooterCmp />
</aura:application>

Next, you need to create a VF page:

<apex:page showHeader="false" standardController="CustomObj__c" extensions="PageExt" 
action="{!Load_method}" cache="false" standardStylesheets="false" docType="html-5.0" 
applyBodyTag="false" applyHtmlTag="true">

<head>
    <meta charset="utf-8" />
    <title>{!theName}</title>
    <meta property="og:title" content="{!theName}" />
    <meta property="og:description" content="{!theDesc}" />
    <meta property="og:image" content="https://Logo.png" />
    <meta property="og:image:secure_url" content="https://Logo.png" />
    <meta name="keywords" content="All your keywords" />
    <apex:includeLightning />

</head>

<body>

    <script>
        $Lightning.use("c:MyLightningApp", function() {
            $Lightning.createComponent("c:MenuCmp", {},
                "lightningMenu",
                function(cmp) {});
        });

        $Lightning.use("c:MyLightningApp", function() {
            $Lightning.createComponent("c:BodyCmp", {
                    "theID": "{!$CurrentPage.parameters.id}"
                },
                "lightningContent",
                function(cmp) {});
        });

        $Lightning.use("c:MyLightningApp", function() {
            $Lightning.createComponent("c:FooterCmp", {},
                "lightningFooter",
                function(cmp) {});
        });
    </script>


    <div id="fb-root"></div>
    <script>
        (function(d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) return;
            js = d.createElement(s);
            js.id = id;
            js.src = 'https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.12&appId=160283181136579&autoLogAppEvents=1';
            fjs.parentNode.insertBefore(js, fjs);
        }(document, 'script', 'facebook-jssdk'));
    </script>


    <div class="homeWrapTest">
        <div class="slds-scope">
            <div id="lightningMenu"></div>
            <div style="display:block; float: right; padding: 10px;">
                <div class="fb-share-button" data-layout="button_count" data-size="small" data-mobile-iframe="true"><a class="fb-xfbml-parse-ignore" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fplugins%2F&amp;src=sdkpreparse">Share</a></div>
            </div>
            <div id="lightningContent"></div>
            <div style="margin-bottom: 25%;"></div>
            <div id="lightningFooter"></div>
        </div>
    </div>
</body>
</apex:page>

You can use the standardController without an extension and action, but I found that it was populating the Name, but not the Description meta tag. This may be because it was a long text field or some other reason. Bottom line is, it works with the ext and action.

Hope this saves someone all the headache I went through...

Related Topic