FAQ Page

Create a frequently asked questions page to reduce support load and improve SEO with structured data.

The FAQ page reduces support tickets by answering common questions before users contact you. Makerkit includes a ready-to-use FAQ page at /faq with native HTML accordions, JSON-LD structured data for SEO rich results, and i18n support. Questions are stored in translation files, making them easy to update and translate.

The FAQ page is a structured list of frequently asked questions with expandable answers, optimized for search engine rich results and support ticket deflection.

  • Use the FAQ page when: you have 5-20 common questions that can be answered in 1-3 sentences each, especially billing, account, and getting-started questions.
  • Use documentation when: answers require step-by-step instructions, code examples, or detailed explanations that exceed a few paragraphs.
  • Use a support system when: questions require personalized responses, access to user data, or back-and-forth conversation.

File Location

The FAQ page is located at apps/web/app/[locale]/(public)/faq/page.tsx.

Page Structure

The FAQ page uses:

  • SitePageHeader for the page title and subtitle
  • Native HTML <details> and <summary> elements for the accordion
  • JSON-LD structured data for SEO
  • A contact CTA button at the bottom

FAQ Item Component

Each FAQ item uses the native <details> element for accessibility and no JavaScript requirement:

function FaqItem({
item,
}: {
item: {
question: string;
answer: string;
};
}) {
return (
<details
className="hover:bg-muted/70 [&:open]:bg-muted/70 transition-all"
>
<summary className="flex items-center justify-between p-4 hover:cursor-pointer">
<h2 className="cursor-pointer font-sans text-base">
{item.question}
</h2>
<ChevronDown className="h-5 transition duration-300 group-open:-rotate-180" />
</summary>
<div className="text-muted-foreground flex flex-col gap-y-2 px-4 pb-2">
{item.answer}
</div>
</details>
);
}

Translations

FAQ content is stored in translation files for internationalization. Add your questions and answers to the translation file apps/web/i18n/messages/en/marketing.json:

{
"faq": "FAQ",
"faqSubtitle": "Frequently asked questions about the platform",
"contactFaq": "If you have any questions, please contact us",
"faqQuestion1": "Do you offer a free trial?",
"faqAnswer1": "Yes, we offer a 14-day free trial. You can cancel at any time during the trial period and you won't be charged.",
"faqQuestion2": "Can I cancel my subscription?",
"faqAnswer2": "You can cancel your subscription at any time. You can do this from your account settings.",
"faqQuestion3": "Where can I find my invoices?",
"faqAnswer3": "You can find your invoices in your account settings.",
"faqQuestion4": "What payment methods do you accept?",
"faqAnswer4": "We accept all major credit cards and PayPal.",
"faqQuestion5": "Can I upgrade or downgrade my plan?",
"faqAnswer5": "Yes, you can upgrade or downgrade your plan at any time. You can do this from your account settings.",
"faqQuestion6": "Do you offer discounts for non-profits?",
"faqAnswer6": "Yes, we offer a 50% discount for non-profits. Please contact us to learn more."
}

For complete i18n setup, see Internationalization.

Adding FAQ Items

To add more FAQ items:

Add the question and answer translations to marketing.json:

{
"faqQuestion7": "Your new question?",
"faqAnswer7": "Your answer here."
}

Add the item to the faqItems array in the page:

const faqItems = [
// ... existing items
{
question: t('faqQuestion7'),
answer: t('faqAnswer7'),
},
];

SEO Structured Data

The page includes JSON-LD structured data for better search engine visibility. This helps search engines display FAQ rich results (expandable answers directly in search results):

const structuredData = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqItems.map((item) => ({
'@type': 'Question',
name: item.question,
acceptedAnswer: {
'@type': 'Answer',
text: item.answer,
},
})),
};

The structured data is rendered as a script tag:

<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>

Page Metadata

The page includes proper metadata for SEO:

export async function generateMetadata(): Promise<Metadata> {
const t = await getTranslations('marketing');
return {
title: t('faq'),
description: t('faqSubtitle'),
};
}

SitePageHeader Component

The SitePageHeader component provides a consistent header style across marketing pages:

import { SitePageHeader } from '../_components/site-page-header';
<SitePageHeader
title={t('faq')}
subtitle={t('faqSubtitle')}
/>

This component is located at apps/web/app/[locale]/(public)/_components/site-page-header.tsx.

Customization

Styling the Accordion

Modify the FaqItem component to change the accordion appearance:

<details className="hover:bg-muted/70 [&:open]:bg-muted/70 transition-all">
{/* Customize hover and open states */}
</details>

Grouping by Category

To group FAQs by category, restructure the data:

const faqCategories = [
{
category: 'Billing',
items: [
{ question: t('faqQuestion1'), answer: t('faqAnswer1') },
{ question: t('faqQuestion2'), answer: t('faqAnswer2') },
],
},
{
category: 'Account',
items: [
{ question: t('faqQuestion3'), answer: t('faqAnswer3') },
],
},
];

Then render with category headers:

{faqCategories.map((category) => (
<div key={category.category}>
<h2 className="text-xl font-semibold mb-4">{category.category}</h2>
<div className="divide-y divide-dashed rounded-md border">
{category.items.map((item, index) => (
<FaqItem key={index} item={item} />
))}
</div>
</div>
))}

Common Pitfalls

  • Too many FAQs: More than 20 questions becomes overwhelming. Move detailed topics to documentation and keep FAQs short.
  • Long answers: FAQ answers should be 1-3 sentences. If an answer needs paragraphs, link to documentation instead.
  • Duplicate structured data: Only one FAQPage schema per page. Don't combine with other FAQ components that also inject schema.
  • Outdated answers: Review FAQs quarterly. Outdated pricing or feature information damages trust.
  • Missing translation keys: Adding a question to the page without the translation key causes a missing translation error. Add both together.
  • Generic questions: "How does it work?" is too vague. Be specific: "How do I invite team members?"
  • No contact fallback: Always include a contact CTA at the bottom for questions not covered.

Frequently Asked Questions

How many FAQs should I include?
Start with 5-10 of your most common support questions. Add more as patterns emerge, but keep the total under 20 for readability.
Do FAQ rich results always appear in Google?
No. Google decides when to show rich results based on relevance and quality. Structured data makes you eligible but doesn't guarantee display.
Can I use Markdown in FAQ answers?
The default implementation uses plain text. For rich formatting, modify the FaqItem component to render Markdown or HTML.
How do I track which FAQs are most viewed?
Add click tracking to the details element's toggle event. Send events to your analytics provider when users expand answers.
Should I duplicate FAQ content in documentation?
Avoid duplication. Link from FAQ answers to detailed documentation pages instead of repeating content.

Next: Legal Pages →