SEO - Generate structured data based on window.records values

I was able to get this to work if I made the content of the SEO:JSON-LD field this:

{
  "@context": "https://schema.org",
  "@type": "Flight",
    "departureAirport": {
      "@type": "Airport",
      "name": "San Francisco Airport",
      "iataCode": "SFO"
    },
    "departureTime": "2017-03-04T20:15:00-08:00",
    "arrivalAirport": {
      "@type": "Airport",
      "name": "John F. Kennedy International Airport",
      "iataCode": "JFK"
    },
    "arrivalTime": "2017-03-05T06:30:00-05:00"
  }
}

I.e., I think you were double-enclosing the data in a <script> tag.

Interesting, thanks for trying. Double-checked that the Airtable field doesn’t already include the tags, and also tried with that exact schema.org definition, but without any luck.

Would you mind sharing your custom code?

I copied and pasted your code, so I don’t think I have anything to offer there!

Maybe it’s actually working for you? How are you going about confirming whether it’s working? What I did was to inspect the DOM of the page in the Google Chrome developer tools, and this is what I found, near the bottom of the page:

Ah, you got me - it is working as expected indeed! I had been looking for the code in the Page Source, rather than Elements within the Dev Tools. Mystery solved - much appreciate the help!

There’s (at least) one more hurdle to get over. I would not assume that the Google crawler can be counted on to wait until this JavaScript runs to add the JSON to the page, before it starts processing. I think this needs to be tested.

If you find that Google isn’t properly waiting and isn’t finding the JSON, then that’s really going to push the ball back to Softr to solve this problem, as I don’t see any way for custom code to get access to the data any sooner.

1 Like

For anyone attempting the same approach, make sure that you make proper use of the quotation marks in the Airtable formula field, e.g.:

'{
  "@context": "http://schema.org",
  "@type": "Flight",
  "provider": {
    "@type": "Airline",
    "name": "' & {airlineName} & '",
  },
  "aircraft": "' & {aircraftModel} & '"
}'

Good call. That remains to be seen indeed. I will keep an eye on it.

For what it’s worth, I didn’t have to do anything fancy with quotation marks. I used a field of type Long Text and did not turn on rich text formatting.

I suspect you’re going to be okay with the custom-code-based approach to generating the JSON, from reading the docs.

Yep, should work well without quotation marks as long as you can use a regular text field. In my case I will only generate the JSON within Airtable itself, and therefore need to use a Formula field for the values to be dynamic.

One more thing to mention about the structure of the JavaScript code above.

Right now what it’s doing is waiting until the DOMContentLoaded event, then running a block of code every 100ms to see if jQuery has been loaded, then when jQuery is loaded, it runs the main body of its code.

This happens to work, but it’s got a couple of problems that could surface in the future in really mysterious ways.

The main problem is that jQuery being loaded is not actually the right thing to wait for. First of all, I don’t believe the body of the code actually depends on jQuery being loaded at all - it doesn’t use it. Second, the thing it actually should be waiting for is the data in window.records to be available. So I think you could remove the test typeof $ != 'undefined' and move the test of window.records up to that same spot where the jQuery test was.

That would also allow you to fix the other problem, which is that today when this code runs, if the main body has an error, the clearInterval() function never gets called, and that means this little block of code will run again every 100ms as long as the page is open. This could impact responsiveness from the user’s point of view. So the solution here is to call clearInterval() at the top of the body instead of the bottom.

1 Like

Hi @dcoletta , great analysis and feedback - I modified the original script from @artur, which wasn’t necessarily intended to load JSON-LD structured data.

Appreciate the suggestions - going to experiment a bit further.

1 Like

@irestmycase @dcoletta @artur
Would anyone be able to share that updated script? I’m managing to put together the JSON in a formula field in Airtable for my purposes (job post and organization structures), but to create the script is where I struggle.

Would much appreciate any code that could be shared.

@Suzie feels like a feature, or something to create a little tutorial for users? Since getting the pages listed on Google is so crucial for everyone :slight_smile:

I would strongly agree that, if this metadata is important for SEO, Softr should generate it automatically, and there should be a way in the studio to specify which fields of a List Details block should be included. The approach in this discussion topic is a proof of concept but it’s way outside what I think of as “no-code”.

Ironically, this is all totally obsolete already, as it would work way better for the whole internet for Google to use machine learning to generate the relevant metadata automatically.

2 Likes

@Tim_ClimatEU this should actually work SEO - Generate structured data based on window.records values - #10 by irestmycase

1 Like

Confirming that it does! :rocket:

Hi all, thank you for the discussion, it is SUPER valuable. I somehow fail to make it work (we’re a bit off the “no-code” zone here).
This is what I did:

  1. in the Airtable table I added a column named “SEO:JSON-LD” with a formula that dynamically creates my JSON. I tested the JSON on the Google kit to test markup, and the JSON passed the test (https://search.google.com/test/rich-results?hl=it)

  2. in the Softr app, on the Page Custom Header Code, I inserted the exact script found above (reported below)

<script>    
    document.addEventListener("DOMContentLoaded", function() {    
        var waitForData = setInterval(function () {
            if (typeof $ != 'undefined') {
                const recordId = getUrlParam('recordId');
                if (window.records && window.records[recordId] && window.records[recordId].record.fields['SEO:JSON-LD']) {
                    
                    const jsonldScript = document.createElement('script');
                    jsonldScript.setAttribute('type', 'application/ld+json');

                    const structuredData = window.records[recordId].record.fields['SEO:JSON-LD'];
                    
                    jsonldScript.textContent = structuredData;
                    
                    console.log(jsonldScript);
                    
                    document.head.appendChild(jsonldScript);
                    
                    clearInterval(waitForData);
                }
            }
        }, 100);

        function getUrlParam(name) {
            const url = new URL(window.location.href);
            let param;
            for(var key of url.searchParams.keys()) {
                if(key.toLowerCase() === name.toLowerCase()) {
                param = url.searchParams.get(name);
                break;
                }
            }
        
            if(!param && name.toLowerCase() === 'recordid') {
                param = getRecordIdFromPath();
            }
            return param;
        }
        
        function getRecordIdFromPath() {
            let pathName = window.location.pathname;
            if (pathName.indexOf('/r/rec') !== -1) {
                pathName = pathName.substr(pathName.indexOf('/r/rec') + 3);
                if (pathName.indexOf("/") !== -1) {
                pathName = pathName(0, pathName.indexOf('/'))
                }
                return pathName;
            }
            return undefined;
        }
    });
</script>
  1. I don’t think I am getting the result desired, as in “Inspect” from my Chrome browser, I get the following

I am pretty sure I am missing something. Can anyone help me find out what?

Thanks

1 Like

I added that exact code in the page’s header’s custom code, but I have the same issue as @squatrito .

This is the error the console is giving me:

Does anyone have an idea what I might be doing wrong?

Just wanted to bump this up in case someone can helo @squatrito and me with the same problem. @artur @irestmycase

I’m also getting this error while trying to get an ID value from a record on a List Details page and then use that ID in a widget embedding script. I’m doing this so I can render a different widget for each item in the list.

<div class="calconic-calculator" data-calculatorid=""></div>
<script>
document.addEventListener("DOMContentLoaded", function() {
  var waitForData = setInterval(function () {
    if (typeof window.records !== 'undefined' && Object.keys(window.records).length > 0) {
      clearInterval(waitForData);
      const calconicId = window.records[Object.keys(window.records)[0]].record.fields.CalconicId;
      var calculatorElement = document.querySelector('.calconic-calculator');
      calculatorElement.dataset.calculatorid = calconicId;
    }
  }, 100);
  
  (function() {
    var qs, j, q, s, d = document, gi = d.getElementById, ce = d.createElement, gt = d.getElementsByTagName,
    id = "calconic_", b = "https://cdn.calconic.com/static/js/";
    if (!gi.call(d, id)) {
      j = ce.call(d, "script");
      j.id = id;
      j.type = "text/javascript";
      j.async = true;
      j.dataset.calconic = true;
      j.src = b + "calconic.min.js";
      q = gt.call(d, "script")[0];
      q.parentNode.insertBefore(j, q);
    }
  })();
});
</script>