Because “educator” is among the many hats that someone in our line of work must wear on any given day, I really cherish those moments of role-reversal when I find one of my clients educating me. It brings a welcome balance to the relationship and a dose of the kind of humility that all anointed “experts” should take from time to time. In a neighboring spirit, I also enjoy those moments when I discover some novel feature or quirk of one of the tools I commonly use that empowers me with a new perspective on the tool and makes me a more capable user.
In January, on one of UpBuild’s first days back following the holidays, I had a doozy of a day that gave me both of these experiences in quick succession. First, a client taught me something about SEO that I didn’t know. Then, in seeking to apply the new wisdom with the pertinent toolkit, I immediately encountered a problem that I’d never seen before. Then, a small amount of research online led me to a solution to that problem, granting me a new and deeper understanding of the toolkit along the way. Any day full of learning like that is worth remembering. If what you learned should be shared, it’s worth blogging about too. So, in that spirit, welcome!
A Question About a Question
My client was getting ready to launch a new FAQ page and requested that I give it a once-over to make sure the usual SEO boxes were ticked: absence of rendering issues, optimized copy and metadata, proper understanding of the heading tag hierarchy, adequate internal cross-linking, etc. I made a few notes suggesting trivial tweaks and passed it back saying it would be good to launch once those had been made. My client responded by asking “What about structured data markup?” I confidently shot back that there was no Schema.org itemtype that I knew of for Q&A-type content. He responded asking about this page: http://schema.org/Question, which looks like this:
Lookit that! A Question entity, and one flexible enough to apply either to forums or to official FAQ pages exactly like my client’s. And I had had no idea. I thanked my client for pointing this out and set about seeing how I might translate this model into a JSON-LD tag for their page, which was a simple list of 18 questions, each with a single answer.
18 Problems and JSON-LD Was All of Them
I then quickly realized that because “Question” was an itemtype unto itself — not an itemprop of a larger itemtype entity that might apply to the page as as whole — each of the 18 questions on the page necessarily had to be marked up as entities unto themselves. There was no obvious way to condense markup for 18 separate entities into a single snippet of JSON-LD. I discovered that there was indeed a QA Page entity itemtype — a subtype of WebPage — which could have applied to the whole page, but that itemtype didn’t welcome itemprops corresponding to questions and answers, in the way that certain other objects in the Schema.org library exist in two inverse forms to suit whichever perspective is more convenient for your markup to take (e.g. mainEntity v. mainEntityOfPage). So I was faced with the task of marking up 18 different questions each as autonomous entities, all sharing space at the top level of the same webpage. The only way I could think to do this, absent some discovery, was by writing 18 separate pieces of JSON-LD, like so:
Et cetera. This validated, but it seemed inelegant. While I couldn’t be precisely sure of the time cost to loading 18 different snippets of JavaScript compared to loading one, it seemed fair to assume that it would be higher. And on a cellular level, it just felt wrong. Even a novice coder like me bristles at the sight of such repetitive declarations; opening a new <script> tag for every question seemed rather like writing an in-class essay assignment in high school and giving each paragraph its own sheet of paper. It’s bulky and wasteful. So I was dissatisfied with this approach from the start and wondered if there were perhaps some way to combine these 18 scripts into one without forcing deference to a single itemtype declaration (represented by “@type” in the JSON-LD). After all, I had 18 separate questions to encode, each with its own “text” and its own “accepted answer”, which meant I couldn’t very well use one single declaration of “Question”. So I started doing what coders of all stripes invariably do in these situations: I Googled around for an answer.
Stack Overflow to the Rescue, Obvs
I don’t even remember what my query was, exactly, though wording it was my most clear and present initial challenge. I think I ultimately went with something like “how to declare multiple instances of the same Schema.org itemtype in one JSON-LD tag”, though that memory might be colored now by the keywords for which I hope this post might eventually be found. Anyway, once I had refined my formulation adequately, there soon appeared in the SERPs the angelic domain that all coders in this position yearn to see: StackOverflow.
The SO post that showed up in my SERPs was this one, which simply asked whether it was valid to post two or more Schema.org JSON-LD tags side-by-side on the same webpage. I already knew that the answer to this was “yes”, and that this situation was precisely what I was trying to avoid, but I wondered whether some comment in the thread might point the way to an answer to my question. Lo and behold, a footnote in the most popular answer held the key:
…two or more top-level items in a single script.
YES. That’s it. Tell me about @graph, you promising link, you!
@graph, You Guys
That link led me to here, a different SO post that I never would have found directly because its title was “JSON-LD Schema.org: Multiple video/image page”, and hence was a bit boxed in, in relevance terms, by its focus on video and image markup. But the essence of the question indicated the same issue that I was facing:
And the most popular answer? It was exactly what I was looking for:
This!!!!
I quickly rewrote my dummy code as shown below and it validated, showing exactly as the previous example did in the Structured Data Testing Tool but with fewer instructions, and, critically, only one $lt;script> tag:
Success!
Why Though
Except… what is @graph? However rudimentary, my grasp of JavaScript is sufficient to help me see that the basic idea is to use an array — as indicated by the [ ] brackets — to dictate that different top-level entity declarations be considered as a group of like items rather than individually. But there’s no mention of @graph anywhere on Schema.org, even though it’s plain to see that it works. So the next thing I did — purely to satisfy lingering curiosity, at this point — was to research the origin and meaning of this JSON-LD specification.
It takes a bit of rummaging around in the W3C page on JSON-LD to sift through all the cross-referential definitions and get to a real understanding, but the provided definition of a JSON-LD “graph” is as follows:
A graph is a labeled directed graph, i.e., a set of nodes connected by edges.
This isn’t as recursive as it seems at first blush. It boils down to a description of JSON-LD’s core essence. True to its name, JSON-LD is a method (JS) for notating linked data (LD) comprised of nodes and edges, wherein a “node” simply refers to a single data point, and an “edge” describes the relationship between two linked nodes. In the case of an entry for, say, War and Peace, one could designate a node for the book itself, a node for Leo Tolstoy, and an edge indicating that the man’s relationship to the book was as its author. And this whole network of relationships would constitute a graph.
So if a graph is “a set of nodes connected by edges”, then every JSON-LD object, on the whole, is a graph, which means that this concept of @graph — a “named” graph, or a “labeled directed” graph, from which is distinguished the “default graph” that is the object on the whole — is just a way of leveraging JavaScript’s existing array capabilities to allow a single JSON-LD graph to describe multiple smaller graphs, in much the same way that an array can contain other arrays, or an object can contain other objects (all arrays in JavaScript are in fact objects of a simplified type, but that’s a post for another time).
This means that in any case wherein I have to describe two or more JSON-LD objects side-by-side at the top level of a webpage, I can use this @graph feature to smush them all together into a single <script> tag, saving valuable line space and load time. The objects don’t even have to be of the same type for this to work; you’ll note that I did declare “@type: https://schema.org/Question” for each. Pretty cool, eh?
I’m rather delighted by the newfound flexibility this has afforded me. It’s strengthened my comprehension, and by extension my power, as a JSON-LD coder, and even if the overall global effect is rather small, this new power of mine is going to save servers some labor. For a client to have led me to this place is especially meaningful; I’m already eternally grateful to them for providing me with my livelihood, but when they actually make me better at my job, so that I can turn around and serve them better still, I realize with special poignancy that with our working relationship, even at its driest, my clients and I actually enrich each other’s lives. In this line of work, feel-good moments don’t come any feel-goodier than that.