FAQ Page

Create a frequently asked questions page to address common user queries.

The FAQ page helps reduce support load by answering common questions. The kit provides a ready-to-use FAQ page with SEO-optimized structured data.

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."
}

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:

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