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:
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."}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.tsxCustomization
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>))}