SEO - Generate structured data based on window.records values

Hi all,

To further optimize SEO, I’m attempting to implement Softr’s recommendation to include structured data for List Detail pages, e.g.:

<script type="application/ld+json">
{
  "@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"
  }
}
</script>

My main challenge, however, is to fetch the values. My current approach is to define each in a different script based on the window.records-approach.

const depAirportCode = window.records[recordId].record.fields['depAirportCode'];

Note that I can print these successfully in the console, thanks to Artur’s script posted here.

Having said this, I am not quite sure whether this is feasible, or a good approach at all. Curious to learn from your experience generating structured data!

This is the right direction. I suspect that it’s going to be a lot of manual work, and I’m trying to imagine how to do it in a more automated way.

I can think of a couple of different ways to make it more automated. The one I would suggest trying first is to move the custom code into a Formula field in Airtable, and then pull the text of that script out of window.records and append it to the page.

Ah, I see what you mean: essentially generating the json in a single field in Airtable and pulling this in to. the page, rather than each field individually.

Haven’t tried generating/storing JSON in Airtable, and it doesn’t feel like the cleanest approach either, but agree that it could be much more efficient.

What about Google Tag Manager? Could it possibly use the data that’s on the page already, rather than pulling in window.records values?

I don’t know Google Tag Manager at all, but just off the top of my head, I’m thinking that’s the opposite direction of information flow from what you want. You’re trying to generate markup from your database that is intended for Google’s crawler to ingest, whereas GTM is about Google and others injecting stuff into your page from the outside.

If you’re not crazy about generating the JSON in an Airtable formula, here are two more approaches.

  1. You could try this approach from unlock-softr.com

  2. You could write some custom code that runs when the page loads, and assembles the JSON with JavaScript string operations, substituting data in from window.records, then injecting it into the page DOM.

I think creating a JSON via formula in Airtable then using a small script to get from the record and create a script tag with that JSON might be the best approach. It might feel scrappy but I think it’s the best option and those customisations always require some hacking :slight_smile:

My question here is why we have to do this manually, and Softr isn’t doing it for us

2 Likes

Much appreciate the help and suggestions. Let me go ahead creating the JSON in Airtable and adding this in a simple script as custom code to include it in the detail pages.

I would love to read this script :slight_smile: thanks

@aar0n Softr could get it from Airtable and put into right place similarly to SEO:Title but can’t fill in all the JSON…

Made a few attempts to insert the structured data stored from an Airtable field (SEO:JSON-LD) into the of the page. While the console.log displays the structured data correctly, it does not actually appear within the .

The line document.head.appendChild(jsonldScript); seems to be failing. Thoughts anyone?

<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>

From the console:

1 Like

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.