Drupal supports a wide range of tools and techniques for improving on page SEO. A well-configured Drupal site will generally have the basics covered for site structure, metadata and integration with the various search engines. In order to get the most out of this solid foundation it is essential to have content which is fresh and interesting which provides useful and valuable information. Part of the strategy here needs to be based on answering questions which are likely to be asked by your users.
Answering questions
Users are increasingly searching by asking questions. Rather than crafting specific search terms, it is easier to ask a question directly. The rise of voice search and home assistants have helped drive this kind of search behaviour. A look at the Google suggestions for a search term is a good indication of what questions people are asking:
How can we structure our content to adapt to this way of searching? The simplest answer is to write authoritative content which addresses the question at hand and to provide a succinct answer at the top of the content. Search engines such as Google will often be able to pick up on authoritative answers when written this way.
When writing content to this new format it will be helpful to phrase your content around the way users will ask questions. Focus on the:
- Who
- What
- Where
- When
- Why
- How
FAQPage metadata
There are more direct ways of letting search engines know that the content is of a question-and-answer format. Search engines such as Google will ingest metadata which is embedded in the page, including content which is specially marked up as being an FAQ.
The FAQPage type is defined on schema.org as follows:
A FAQPage is a WebPage presenting one or more "Frequently asked questions"
The implementation details for FAQPage can be found on this documentation page. In a nutshell, content can be marked up with JSON-LD or Microdata. Content is marked up as being a question or answer. Here is an example in Microdata format:
<html itemscope itemtype="https://schema.org/FAQPage">
<head>
</head>
<body>
<h1>
Frequently Asked Questions(FAQ)
</h1>
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">
<h2 itemprop="name">What is the return policy?</h2>
<div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
Most unopened items in new condition and returned within <b>90 days</b> will receive a refund or exchange. Some items have a modified return policy noted on the receipt or packing slip. Items that are opened or damaged or do not have a receipt may be denied a refund or exchange. Items purchased online or in-store may be returned to any store.
<br /><p>Online purchases may be returned via a major parcel carrier. <a href="http://example.com/returns"> Click here </a> to initiate a return.</p>
</div>
</div>
</div>
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">
<h2 itemprop="name">How long does it take to process a refund?</h2>
<div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
We will reimburse you for returned items in the same way you paid for them. For example, any amounts deducted from a gift card will be credited back to a gift card. For returns by mail, once we receive your return, we will process it within 4–5 business days. It may take up to 7 days after we process the return to reflect in your account, depending on your financial institution's processing time.
</div>
</div>
</div>
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">
<h2 itemprop="name">What is the policy for late/non-delivery of items ordered online?</h2>
<div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
Our local teams work diligently to make sure that your order arrives on time, within our normal delivery hours of 9AM to 8PM in the recipient's time zone. During busy holiday periods like Christmas, Valentine's and Mother's Day, we may extend our delivery hours before 9AM and after 8PM to ensure that all gifts are delivered on time. If for any reason your gift does not arrive on time, our dedicated Customer Service agents will do everything they can to help successfully resolve your issue.
<br/> <p><a href="https://example.com/orders/">Click here</a> to complete the form with your order-related question(s).</p>
</div>
</div>
</div>
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">
<h2 itemprop="name">When will my credit card be charged?</h2>
<div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
We'll attempt to securely charge your credit card at the point of purchase online. If there's a problem, you'll be notified on the spot and prompted to use another card. Once we receive verification of sufficient funds, your payment will be completed and transferred securely to us. Your account will be charged in 24 to 48 hours.
</div>
</div>
</div>
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">
<h2 itemprop="name">Will I be charged sales tax for online orders?</h2>
<div itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
Local and State sales tax will be collected if your recipient's mailing address is in:
<ul>
<li>Arizona</li>
<li>California</li>
<li>Colorado</li>
</ul>
</div>
</div>
</div>
</body>
</html>
Implementing similar markup on your pages will indicate the structure of your content so that it can be more easily consumed and understood, increasing the chances of being used in search result pages.
What does this look like in practice?
Content which has been marked up as an FAQPage will increase its chances of being included in search result pages. In this example below we can see how an FAQPage enabled page displays the questions and answers in an accordion and augments the result which is displayed.
Accordion groups
When considering the nature of questions and answers, and FAQs in general, the accordion is a component which comes to mind. Its general structure matches the needs of a FAQ. It is also a typical component with an implementation in design systems. For example, at Morpht we often work with Bootstrap and the Australian Government Design System, both of which have an accordion component.
From an implementation perspective, it made sense to augment these components with an FAQ capability. In Drupal, accordion groups can be implemented with the Paragraphs module. Adding a “Make FAQ” checkbox to an accordion group was a natural and straightforward way to give editors the control to identify them to be augmented with the FAQPage markup. FAQs are easy to make, opening the way for editors to improve the SEO of the content they are writing.
At Morpht we have adopted this approach in the accordions we have implemented in our starter sites Convivial and Convivial for GovCMS.
Adding the markup in Drupal
We have included some implementation details below to help get you started in implementing FAQPage markup in your own projects. Feel free to adapt the technique below as needed.
Adding a page type to the HTML
In order to have structured data working we need to add the FAQPage itemscope to the HTML. This is achieved by preprocessing html.
/**
* Implements template_preprocess_html().
*/
function yourtheme_preprocess_html(&$variables) {
// Add structured data attributes to <html>.
// @see https://developers.google.com/search/docs/advanced/structured-data/faqpage
$html_item_types = &drupal_static('yourtheme_html_item_types', []);
if (!empty($html_item_types)) {
$variables['html_attributes']['itemscope'] = '';
$variables['html_attributes']['itemtype'] = implode(' ', $html_item_types);
}
}
The drupal_static
function provides a central static variable storage base.
Add Question and Answer markup to the accordion template
Each FAQ item needs to have itemprop
and itemtype
attributes. Here is an example of the accordion template:
{# Prepare variable for paragraph id. #}
{% set accordion_item_id = paragraph.id() %}
{%
set classes = [
'accordion',
'paragraph',
'paragraph--type--' ~ paragraph.bundle|clean_class,
view_mode ? 'paragraph--view-mode--' ~ view_mode|clean_class,
]
%}
{% block paragraph %}
{%
include '@yourtheme/paragraphs/accordion/accordion-card.twig' with {
id: accordion_item_id,
title: content.field_heading[0],
body: content.field_accordion_body[0],
orientation: paragraph.field_accordion_orientation.value,
faq: paragraph.field_accordion_make_faq.value
} only
%}
{% endblock paragraph %}
And accordion-card.twig
{% set expanded = orientation == 'open' ? 'true' : 'false' %}
{% set accordion_button_classes = [
'accordion-button ',
expanded == 'false' ? 'collapsed'
] %}
{% set accordion_collapse_classes = [
'accordion-collapse',
'collapse',
expanded == 'true' ? 'show'
] %}
{% block content %}
{% if faq %}
<div itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">
{% endif %}
<div class="accordion-item">
<div class="accordion-header" id="accordion-{{ id }}">
<button
{{ create_attribute().addClass(accordion_button_classes) }}
type="button"
data-bs-toggle="collapse" data-bs-target="#collapse-{{ id }}"
aria-expanded={{ expanded }} aria-controls="collapse-{{ id }}"
{% if faq %}itemprop="name"{% endif %}
>
{{ title }}
</button>
</div>
<div
{{ create_attribute().addClass(accordion_collapse_classes) }}
id="collapse-{{ id }}"
aria-labelledby="accordion-{{ id }}"
{% if accordion_group_ref %}
data-bs-parent="#accordion-group-{{ accordion_group_ref }}"
{% endif %}
>
{% if faq %}
<div class="accordion-body" itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
{{ body }}
</div>
</div>
{% else %}
<div class="accordion-body">
{{ body }}
</div>
{% endif %}
</div>
</div>
{% if faq %}
</div>
{% endif %}
{% endblock %}
Attributes are passed to paragraph by a custom function triggered in template_preprocess_paragraph()
/**
* Attach structured data.
*/
function _yourtheme_govcms_attach_structured_data(FieldableEntityInterface $entity, array &$build, $attributes_key) {
// Add FAQPage structured data attributes if field is enabled.
$html_item_types = &drupal_static('yourtheme_html_item_types', []);
if ($entity->bundle() === 'accordion' && !empty($entity->get('field_accordion_make_faq')->value)) {
$html_item_types['FAQPage'] = 'https://schema.org/FAQPage';
}
}
The final html looks like:
<div itemscope="" itemprop="mainEntity" itemtype="https://schema.org/Question">
<div class="accordion-item">
<div class="accordion-header" id="accordion-3539">
<button class="accordion-button " type="button" data-bs-toggle="collapse" data-bs-target="#collapse-3539" aria-expanded="true" aria-controls="collapse-3539" itemprop="name">
Accordion
</button>
</div>
<div class="accordion-collapse collapse show" id="collapse-3539" aria-labelledby="accordion-3539">
<div class="accordion-body" itemscope="" itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">
<div itemprop="text">
<p>Accordion item body - Colour Palette - Alt</p>
</div>
</div>
</div>
</div>
</div>
YMMV. Hopefully the above gives you an idea of how we were able to adapt it to the accordions.
Conclusion
Search engine optimization these days is largely content driven. The search behaviour of users has evolved to moving towards asking questions with the expectation that a ready answer will be provided. Structuring content around the question and answer format is a way to move with the times. The addition of FAQPage markup is a technical solution which can help bring the content to the top of search engine results pages. Doing so will increase the visibility of your content, improve the exposure of your site and ultimately improve the traffic flowing through to your site.
At Morpht, we have used this technique with some success. For pages which are ranking well, we have seen a boost in the exposure we receive through the augmentation of the search result. If you have any feedback about how you have used FAQPages or any other suggestions, please leave them in the comments below.