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:
SitePageHeaderfor 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.
Related Documentation
- Internationalization - Multi-language FAQ support
- Documentation - Detailed guides for complex topics
- Landing Page - Marketing page components
Frequently Asked Questions
How many FAQs should I include?
Do FAQ rich results always appear in Google?
Can I use Markdown in FAQ answers?
How do I track which FAQs are most viewed?
Should I duplicate FAQ content in documentation?
Next: Legal Pages →