@zoyth/simple-site-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +572 -0
- package/bin/create-simple-site.js +390 -0
- package/bin/simple-site.js +664 -0
- package/dist/client.js +135 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +107 -0
- package/dist/client.mjs.map +1 -0
- package/dist/components/index.d.mts +3936 -0
- package/dist/components/index.d.ts +3936 -0
- package/dist/components/index.js +38265 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +38173 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.d.mts +298 -0
- package/dist/config/index.d.ts +298 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +1 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/index.d.mts +2184 -0
- package/dist/index.d.ts +2184 -0
- package/dist/index.js +1713 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1605 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/i18n/index.js +665 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/index.mjs +621 -0
- package/dist/lib/i18n/index.mjs.map +1 -0
- package/docs/DOCUMENTATION-STRUCTURE.md +1156 -0
- package/docs/EXPORTS.md +125 -0
- package/docs/PERFORMANCE.md +757 -0
- package/docs/POLICY-PAGES.md +867 -0
- package/docs/ROADMAP.md +334 -0
- package/docs/SEO.md +455 -0
- package/docs/SITEMAP.md +708 -0
- package/docs/STRUCTURED-DATA.md +671 -0
- package/docs/accessibility/common-patterns.md +529 -0
- package/docs/accessibility/keyboard-navigation.md +263 -0
- package/docs/accessibility/overview.md +122 -0
- package/docs/accessibility/screen-readers.md +311 -0
- package/docs/accessibility/wcag-compliance.md +159 -0
- package/docs/api/README.md +164 -0
- package/docs/api/components/Accessibility.md +356 -0
- package/docs/api/components/Button.md +240 -0
- package/docs/api/components/HeroSection.md +306 -0
- package/docs/architecture/decisions.md +449 -0
- package/docs/components/AnalyticsTracker.md +58 -0
- package/docs/components/AnimatedCounter.md +48 -0
- package/docs/components/AnimatedSection.md +56 -0
- package/docs/components/BlogCard.md +42 -0
- package/docs/components/Checkbox.md +56 -0
- package/docs/components/CodeBlock.md +52 -0
- package/docs/components/ComparisonTable.md +40 -0
- package/docs/components/ComponentDemo.md +38 -0
- package/docs/components/CountdownTimer.md +51 -0
- package/docs/components/ExitIntentModal.md +56 -0
- package/docs/components/FAQAccordion.md +66 -0
- package/docs/components/FeaturesGrid.md +55 -0
- package/docs/components/FileUpload.md +54 -0
- package/docs/components/I18nMetaTags.md +55 -0
- package/docs/components/Icon.md +53 -0
- package/docs/components/LazySection.md +46 -0
- package/docs/components/LiveProof.md +53 -0
- package/docs/components/LoadingSpinner.md +46 -0
- package/docs/components/MultiStepForm.md +48 -0
- package/docs/components/PolicyLayout.md +55 -0
- package/docs/components/PricingTable.md +49 -0
- package/docs/components/Radio.md +59 -0
- package/docs/components/SEOMetaTags.md +58 -0
- package/docs/components/ScriptInjector.md +50 -0
- package/docs/components/Select.md +72 -0
- package/docs/components/Skeleton.md +47 -0
- package/docs/components/StatsSection.md +48 -0
- package/docs/components/StickyBar.md +62 -0
- package/docs/components/StructuredData.md +99 -0
- package/docs/components/StyleGuide.md +46 -0
- package/docs/components/TableOfContents.md +47 -0
- package/docs/components/TestimonialCarousel.md +42 -0
- package/docs/components/Timeline.md +51 -0
- package/docs/components/Toast.md +59 -0
- package/docs/components/TrackedLink.md +62 -0
- package/docs/components/TrustBadges.md +44 -0
- package/docs/components/conversion/MobileCTA.md +363 -0
- package/docs/components/forms/ContactForm.md +75 -0
- package/docs/components/forms/FormField.md +74 -0
- package/docs/components/layout/Footer.md +601 -0
- package/docs/components/layout/Header.md +549 -0
- package/docs/components/layout/LanguageSelector.md +54 -0
- package/docs/components/layout/LanguageSwitcher.md +24 -0
- package/docs/components/overview.md +447 -0
- package/docs/components/sections/AboutSection.md +48 -0
- package/docs/components/sections/CTASection.md +596 -0
- package/docs/components/sections/CaseStudySection.md +47 -0
- package/docs/components/sections/ContactSection.md +599 -0
- package/docs/components/sections/FeatureSection.md +44 -0
- package/docs/components/sections/HeroSection.md +404 -0
- package/docs/components/sections/LogosSection.md +47 -0
- package/docs/components/sections/PersonalTaxesSection.md +23 -0
- package/docs/components/sections/RecruitingSection.md +23 -0
- package/docs/components/sections/SecurePortalSection.md +23 -0
- package/docs/components/sections/ServicePageLayout.md +52 -0
- package/docs/components/sections/ServicesSection.md +49 -0
- package/docs/components/sections/TestimonialSection.md +44 -0
- package/docs/components/sections/WhyChooseUsSection.md +54 -0
- package/docs/components/ui/Breadcrumb.md +70 -0
- package/docs/components/ui/Button.md +514 -0
- package/docs/components/ui/Card.md +501 -0
- package/docs/components/ui/Input.md +54 -0
- package/docs/components/ui/MobileLinks.md +43 -0
- package/docs/components/ui/Modal.md +60 -0
- package/docs/components/ui/Tabs.md +62 -0
- package/docs/components/ui/Textarea.md +52 -0
- package/docs/core-concepts/configuration-driven.md +552 -0
- package/docs/core-concepts/overview.md +351 -0
- package/docs/features/accessibility/README.md +73 -0
- package/docs/features/accessibility/aria-support.md +177 -0
- package/docs/features/accessibility/color-contrast.md +155 -0
- package/docs/features/accessibility/focus-management.md +187 -0
- package/docs/features/accessibility/testing.md +196 -0
- package/docs/features/analytics/README.md +51 -0
- package/docs/features/analytics/ab-testing.md +171 -0
- package/docs/features/analytics/conversion-tracking.md +207 -0
- package/docs/features/analytics/custom-events.md +219 -0
- package/docs/features/analytics/privacy.md +198 -0
- package/docs/features/analytics/setup.md +114 -0
- package/docs/features/analytics/tracking-events.md +224 -0
- package/docs/features/i18n/README.md +51 -0
- package/docs/features/i18n/best-practices.md +273 -0
- package/docs/features/i18n/configuration.md +84 -0
- package/docs/features/i18n/formatting.md +133 -0
- package/docs/features/i18n/locale-detection.md +122 -0
- package/docs/features/i18n/routing.md +99 -0
- package/docs/features/i18n/rtl-support.md +191 -0
- package/docs/features/i18n/translations.md +129 -0
- package/docs/features/internationalization.md +595 -0
- package/docs/features/performance/README.md +77 -0
- package/docs/features/performance/bundle-size.md +134 -0
- package/docs/features/performance/caching.md +131 -0
- package/docs/features/performance/code-splitting.md +121 -0
- package/docs/features/performance/image-optimization.md +110 -0
- package/docs/features/performance/lazy-loading.md +92 -0
- package/docs/features/performance/monitoring.md +148 -0
- package/docs/features/seo/README.md +51 -0
- package/docs/features/seo/best-practices.md +184 -0
- package/docs/features/seo/canonical-urls.md +182 -0
- package/docs/features/seo/meta-tags.md +126 -0
- package/docs/features/seo/open-graph.md +166 -0
- package/docs/features/seo/robots-txt.md +146 -0
- package/docs/features/seo/sitemaps.md +162 -0
- package/docs/features/seo/structured-data.md +166 -0
- package/docs/getting-started/installation.md +292 -0
- package/docs/getting-started/introduction.md +195 -0
- package/docs/getting-started/quick-start.md +460 -0
- package/docs/guides/analytics-setup.md +616 -0
- package/docs/i18n/CONFIGURATION.md +353 -0
- package/docs/i18n/EXAMPLES.md +402 -0
- package/docs/i18n/MIGRATION.md +260 -0
- package/docs/i18n/SEO.md +392 -0
- package/docs/i18n/STATIC-GENERATION-FIX.md +71 -0
- package/docs/migration/changelog.md +136 -0
- package/docs/migration/overview.md +233 -0
- package/docs/recipes/adding-animations.md +475 -0
- package/docs/recipes/forms-with-validation.md +393 -0
- package/package.json +152 -0
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Policy Pages Guide
|
|
4
|
+
|
|
5
|
+
Comprehensive guide for creating and managing policy and legal document pages using markdown files.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Policy pages (privacy policies, terms of service, legal documents) are perfect candidates for markdown because they:
|
|
10
|
+
|
|
11
|
+
- ✅ Are primarily text-based content
|
|
12
|
+
- ✅ Change occasionally but not frequently
|
|
13
|
+
- ✅ Need to be readable and editable by non-technical people
|
|
14
|
+
- ✅ Benefit from clean version control
|
|
15
|
+
- ✅ Require proper SEO optimization
|
|
16
|
+
- ✅ Often exist in multiple languages
|
|
17
|
+
|
|
18
|
+
The framework provides components and utilities to transform markdown files into beautifully formatted, SEO-optimized policy pages with automatic table of contents and proper styling.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Install Dependencies
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install next-mdx-remote
|
|
28
|
+
npm install -D @tailwindcss/typography
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Configure Tailwind Typography
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
// tailwind.config.js
|
|
35
|
+
module.exports = {
|
|
36
|
+
plugins: [
|
|
37
|
+
require('@tailwindcss/typography'),
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 3. Create Policy Directory
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
mkdir -p src/content/policies
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 4. Create Markdown File
|
|
49
|
+
|
|
50
|
+
```markdown
|
|
51
|
+
<!-- src/content/policies/privacy-policy.en.md -->
|
|
52
|
+
---
|
|
53
|
+
title: "Privacy Policy"
|
|
54
|
+
lastUpdated: "2026-02-03"
|
|
55
|
+
description: "How we protect your personal data"
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# Privacy Policy
|
|
59
|
+
|
|
60
|
+
Last updated: February 3, 2026
|
|
61
|
+
|
|
62
|
+
## 1. Information We Collect
|
|
63
|
+
|
|
64
|
+
We collect the following types of information...
|
|
65
|
+
|
|
66
|
+
## 2. How We Use Your Information
|
|
67
|
+
|
|
68
|
+
We use your information to...
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 5. Create Page Component
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// app/[locale]/privacy/page.tsx
|
|
75
|
+
import { loadPolicy } from 'simple-site-framework/lib/content';
|
|
76
|
+
import { PolicyLayout } from 'simple-site-framework';
|
|
77
|
+
|
|
78
|
+
export default async function PrivacyPage({
|
|
79
|
+
params
|
|
80
|
+
}: {
|
|
81
|
+
params: { locale: string }
|
|
82
|
+
}) {
|
|
83
|
+
const { content, metadata } = await loadPolicy('privacy-policy', params.locale);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<PolicyLayout
|
|
87
|
+
title={metadata.title}
|
|
88
|
+
lastUpdated={metadata.lastUpdated}
|
|
89
|
+
locale={params.locale}
|
|
90
|
+
>
|
|
91
|
+
{content}
|
|
92
|
+
</PolicyLayout>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Generate static pages for all locales
|
|
97
|
+
export async function generateStaticParams() {
|
|
98
|
+
return [
|
|
99
|
+
{ locale: 'en' },
|
|
100
|
+
{ locale: 'fr' },
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// SEO metadata
|
|
105
|
+
export async function generateMetadata({ params }: { params: { locale: string } }) {
|
|
106
|
+
const { metadata } = await loadPolicy('privacy-policy', params.locale);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
title: metadata.title,
|
|
110
|
+
description: metadata.description,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## File Structure
|
|
118
|
+
|
|
119
|
+
### Recommended Directory Structure
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
project/
|
|
123
|
+
├── src/
|
|
124
|
+
│ ├── content/
|
|
125
|
+
│ │ └── policies/
|
|
126
|
+
│ │ ├── privacy-policy.en.md
|
|
127
|
+
│ │ ├── privacy-policy.fr.md
|
|
128
|
+
│ │ ├── terms-of-service.en.md
|
|
129
|
+
│ │ ├── terms-of-service.fr.md
|
|
130
|
+
│ │ ├── cookie-policy.en.md
|
|
131
|
+
│ │ ├── cookie-policy.fr.md
|
|
132
|
+
│ │ ├── dpa.en.md # Data Processing Agreement
|
|
133
|
+
│ │ └── dpa.fr.md
|
|
134
|
+
│ └── app/
|
|
135
|
+
│ └── [locale]/
|
|
136
|
+
│ ├── privacy/
|
|
137
|
+
│ │ └── page.tsx
|
|
138
|
+
│ ├── terms/
|
|
139
|
+
│ │ └── page.tsx
|
|
140
|
+
│ └── cookies/
|
|
141
|
+
│ └── page.tsx
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### File Naming Convention
|
|
145
|
+
|
|
146
|
+
**Format:** `{slug}.{locale}.md`
|
|
147
|
+
|
|
148
|
+
- **slug**: Kebab-case identifier (e.g., `privacy-policy`, `terms-of-service`)
|
|
149
|
+
- **locale**: Language code (e.g., `en`, `fr`, `es`, `en-US`)
|
|
150
|
+
- **extension**: `.md` for markdown, `.mdx` for MDX (markdown + components)
|
|
151
|
+
|
|
152
|
+
**Examples:**
|
|
153
|
+
- ✅ `privacy-policy.en.md`
|
|
154
|
+
- ✅ `terms-of-service.fr.md`
|
|
155
|
+
- ✅ `cookie-policy.en-US.mdx`
|
|
156
|
+
- ❌ `privacy_policy.md` (missing locale)
|
|
157
|
+
- ❌ `Privacy Policy.en.md` (spaces not allowed)
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Markdown Format
|
|
162
|
+
|
|
163
|
+
### Frontmatter (Required)
|
|
164
|
+
|
|
165
|
+
Every policy markdown file must start with YAML frontmatter:
|
|
166
|
+
|
|
167
|
+
```markdown
|
|
168
|
+
---
|
|
169
|
+
title: "Privacy Policy"
|
|
170
|
+
lastUpdated: "2026-02-03"
|
|
171
|
+
description: "How we protect your personal data"
|
|
172
|
+
---
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Required Fields:**
|
|
176
|
+
- `title` (string): Policy title displayed in header
|
|
177
|
+
- `lastUpdated` (string): ISO date or formatted date string
|
|
178
|
+
|
|
179
|
+
**Optional Fields:**
|
|
180
|
+
- `description` (string): SEO meta description
|
|
181
|
+
- Custom fields: Add any additional metadata
|
|
182
|
+
|
|
183
|
+
### Markdown Content
|
|
184
|
+
|
|
185
|
+
Use standard markdown syntax:
|
|
186
|
+
|
|
187
|
+
```markdown
|
|
188
|
+
# Main Heading (H1)
|
|
189
|
+
|
|
190
|
+
This is a paragraph with **bold text** and *italic text*.
|
|
191
|
+
|
|
192
|
+
## Section Heading (H2)
|
|
193
|
+
|
|
194
|
+
### Subsection (H3)
|
|
195
|
+
|
|
196
|
+
- Bullet point 1
|
|
197
|
+
- Bullet point 2
|
|
198
|
+
- Nested bullet
|
|
199
|
+
|
|
200
|
+
1. Numbered item 1
|
|
201
|
+
2. Numbered item 2
|
|
202
|
+
|
|
203
|
+
[Link text](https://example.com)
|
|
204
|
+
|
|
205
|
+
> Blockquote for important notices
|
|
206
|
+
|
|
207
|
+
`Inline code` for technical terms
|
|
208
|
+
|
|
209
|
+
\```
|
|
210
|
+
Code block
|
|
211
|
+
\```
|
|
212
|
+
|
|
213
|
+
| Column 1 | Column 2 |
|
|
214
|
+
|----------|----------|
|
|
215
|
+
| Data 1 | Data 2 |
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Heading Hierarchy Best Practices
|
|
219
|
+
|
|
220
|
+
- Use only ONE H1 (`#`) for the main title
|
|
221
|
+
- Use H2 (`##`) for main sections
|
|
222
|
+
- Use H3 (`###`) for subsections
|
|
223
|
+
- Use H4 (`####`) sparingly for sub-subsections
|
|
224
|
+
- Don't skip levels (H2 → H4 is bad)
|
|
225
|
+
|
|
226
|
+
**Good:**
|
|
227
|
+
```markdown
|
|
228
|
+
# Privacy Policy
|
|
229
|
+
|
|
230
|
+
## 1. Information We Collect
|
|
231
|
+
|
|
232
|
+
### 1.1 Account Information
|
|
233
|
+
|
|
234
|
+
### 1.2 Usage Data
|
|
235
|
+
|
|
236
|
+
## 2. How We Use Your Information
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Bad:**
|
|
240
|
+
```markdown
|
|
241
|
+
# Privacy Policy
|
|
242
|
+
|
|
243
|
+
#### 1. Information ← Don't skip to H4
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Components
|
|
249
|
+
|
|
250
|
+
### PolicyLayout
|
|
251
|
+
|
|
252
|
+
Main layout component for policy pages.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { PolicyLayout } from 'simple-site-framework';
|
|
256
|
+
|
|
257
|
+
<PolicyLayout
|
|
258
|
+
title={metadata.title}
|
|
259
|
+
lastUpdated={metadata.lastUpdated}
|
|
260
|
+
locale={locale}
|
|
261
|
+
showToc={true} // Optional, default: true
|
|
262
|
+
contactText="Questions?" // Optional
|
|
263
|
+
contactHref="/contact" // Optional
|
|
264
|
+
>
|
|
265
|
+
{content}
|
|
266
|
+
</PolicyLayout>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Props:**
|
|
270
|
+
|
|
271
|
+
| Prop | Type | Default | Description |
|
|
272
|
+
|------|------|---------|-------------|
|
|
273
|
+
| `title` | string | Required | Policy title |
|
|
274
|
+
| `lastUpdated` | string | Required | Last updated date |
|
|
275
|
+
| `locale` | string | Required | Locale for date formatting |
|
|
276
|
+
| `children` | ReactNode | Required | Policy content (from MDX) |
|
|
277
|
+
| `showToc` | boolean | `true` | Show table of contents |
|
|
278
|
+
| `contactText` | string | `"Questions about this policy?"` | Footer contact text |
|
|
279
|
+
| `contactHref` | string | `"/{locale}/contact"` | Footer contact link |
|
|
280
|
+
| `className` | string | - | Additional CSS classes |
|
|
281
|
+
|
|
282
|
+
### TableOfContents
|
|
283
|
+
|
|
284
|
+
Automatically generated navigation sidebar.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { TableOfContents } from 'simple-site-framework';
|
|
288
|
+
|
|
289
|
+
<TableOfContents
|
|
290
|
+
title="On This Page"
|
|
291
|
+
includeLevels={[2, 3]}
|
|
292
|
+
containerSelector="article"
|
|
293
|
+
/>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Props:**
|
|
297
|
+
|
|
298
|
+
| Prop | Type | Default | Description |
|
|
299
|
+
|------|------|---------|-------------|
|
|
300
|
+
| `title` | string | `"Table of Contents"` | TOC heading |
|
|
301
|
+
| `includeLevels` | number[] | `[2, 3]` | Heading levels to include |
|
|
302
|
+
| `containerSelector` | string | `"article"` | CSS selector for content |
|
|
303
|
+
| `className` | string | - | Additional CSS classes |
|
|
304
|
+
|
|
305
|
+
**Features:**
|
|
306
|
+
- Auto-extracts headings from page
|
|
307
|
+
- Smooth scroll to sections
|
|
308
|
+
- Scroll spy (highlights current section)
|
|
309
|
+
- Sticky positioning
|
|
310
|
+
- Responsive (collapses on mobile)
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Utility Functions
|
|
315
|
+
|
|
316
|
+
### loadPolicy()
|
|
317
|
+
|
|
318
|
+
Load and compile a policy markdown file.
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { loadPolicy } from 'simple-site-framework/lib/content';
|
|
322
|
+
|
|
323
|
+
const { content, metadata, slug, locale } = await loadPolicy(
|
|
324
|
+
'privacy-policy', // slug
|
|
325
|
+
'en', // locale
|
|
326
|
+
'src/content/policies' // optional: custom directory
|
|
327
|
+
);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Parameters:**
|
|
331
|
+
- `slug` (string): Policy slug (filename without locale/extension)
|
|
332
|
+
- `locale` (string): Locale code
|
|
333
|
+
- `contentDir` (string, optional): Custom directory path, default: `'src/content/policies'`
|
|
334
|
+
|
|
335
|
+
**Returns:**
|
|
336
|
+
```typescript
|
|
337
|
+
{
|
|
338
|
+
content: React.ReactElement, // Compiled MDX content
|
|
339
|
+
metadata: PolicyMetadata, // Frontmatter data
|
|
340
|
+
slug: string, // Policy slug
|
|
341
|
+
locale: string // Locale
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Throws:**
|
|
346
|
+
- Error if file not found
|
|
347
|
+
- Error if required frontmatter missing
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### getPolicySlugs()
|
|
352
|
+
|
|
353
|
+
Get all available policy slugs.
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { getPolicySlugs } from 'simple-site-framework/lib/content';
|
|
357
|
+
|
|
358
|
+
const slugs = getPolicySlugs();
|
|
359
|
+
// Returns: ['privacy-policy', 'terms-of-service', 'cookie-policy']
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Use case:** Generate static paths for all policies
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
export async function generateStaticParams() {
|
|
366
|
+
const slugs = getPolicySlugs();
|
|
367
|
+
const locales = ['en', 'fr'];
|
|
368
|
+
|
|
369
|
+
return slugs.flatMap(slug =>
|
|
370
|
+
locales.map(locale => ({ slug, locale }))
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
### getAllPolicies()
|
|
378
|
+
|
|
379
|
+
Get metadata for all policies in a specific locale.
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { getAllPolicies } from 'simple-site-framework/lib/content';
|
|
383
|
+
|
|
384
|
+
const policies = await getAllPolicies('en');
|
|
385
|
+
|
|
386
|
+
// Returns array of:
|
|
387
|
+
// [
|
|
388
|
+
// { slug: 'privacy-policy', locale: 'en', metadata: {...} },
|
|
389
|
+
// { slug: 'terms-of-service', locale: 'en', metadata: {...} }
|
|
390
|
+
// ]
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Use case:** Generate policy index page
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
export default async function PoliciesPage({ params }) {
|
|
397
|
+
const policies = await getAllPolicies(params.locale);
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<ul>
|
|
401
|
+
{policies.map(policy => (
|
|
402
|
+
<li key={policy.slug}>
|
|
403
|
+
<Link href={`/${params.locale}/policies/${policy.slug}`}>
|
|
404
|
+
{policy.metadata.title}
|
|
405
|
+
</Link>
|
|
406
|
+
<p>{policy.metadata.description}</p>
|
|
407
|
+
</li>
|
|
408
|
+
))}
|
|
409
|
+
</ul>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
### getPolicyLocales()
|
|
417
|
+
|
|
418
|
+
Get available locales for a specific policy.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { getPolicyLocales } from 'simple-site-framework/lib/content';
|
|
422
|
+
|
|
423
|
+
const locales = getPolicyLocales('privacy-policy');
|
|
424
|
+
// Returns: ['en', 'fr', 'es']
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**Use case:** Generate language switcher for policy pages
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Multi-Language Support
|
|
432
|
+
|
|
433
|
+
### Creating Translations
|
|
434
|
+
|
|
435
|
+
Create one markdown file per language:
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
src/content/policies/
|
|
439
|
+
├── privacy-policy.en.md
|
|
440
|
+
├── privacy-policy.fr.md
|
|
441
|
+
├── privacy-policy.es.md
|
|
442
|
+
└── privacy-policy.de.md
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Language Switcher
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
import { getPolicyLocales } from 'simple-site-framework/lib/content';
|
|
449
|
+
import { LanguageSelector } from 'simple-site-framework';
|
|
450
|
+
|
|
451
|
+
export default async function PolicyPage({ params }) {
|
|
452
|
+
const availableLocales = getPolicyLocales(params.slug);
|
|
453
|
+
|
|
454
|
+
return (
|
|
455
|
+
<>
|
|
456
|
+
<LanguageSelector
|
|
457
|
+
currentLocale={params.locale}
|
|
458
|
+
availableLocales={availableLocales} // Only show available translations
|
|
459
|
+
/>
|
|
460
|
+
{/* Policy content */}
|
|
461
|
+
</>
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Handling Missing Translations
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import { loadPolicy } from 'simple-site-framework/lib/content';
|
|
470
|
+
import { notFound } from 'next/navigation';
|
|
471
|
+
|
|
472
|
+
export default async function PolicyPage({ params }) {
|
|
473
|
+
try {
|
|
474
|
+
const policy = await loadPolicy(params.slug, params.locale);
|
|
475
|
+
return <PolicyLayout {...policy} />;
|
|
476
|
+
} catch (error) {
|
|
477
|
+
// Policy doesn't exist for this locale
|
|
478
|
+
notFound();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## SEO Optimization
|
|
486
|
+
|
|
487
|
+
### Meta Tags
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
export async function generateMetadata({ params }) {
|
|
491
|
+
const { metadata } = await loadPolicy(params.slug, params.locale);
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
title: `${metadata.title} | Your Company`,
|
|
495
|
+
description: metadata.description,
|
|
496
|
+
openGraph: {
|
|
497
|
+
title: metadata.title,
|
|
498
|
+
description: metadata.description,
|
|
499
|
+
type: 'website',
|
|
500
|
+
},
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Canonical URLs
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
export async function generateMetadata({ params }) {
|
|
509
|
+
return {
|
|
510
|
+
alternates: {
|
|
511
|
+
canonical: `https://example.com/${params.locale}/${params.slug}`,
|
|
512
|
+
languages: {
|
|
513
|
+
'en': `https://example.com/en/${params.slug}`,
|
|
514
|
+
'fr': `https://example.com/fr/${params.slug}`,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Indexing
|
|
522
|
+
|
|
523
|
+
Policy pages SHOULD be indexed (unlike some other legal pages):
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
export async function generateMetadata() {
|
|
527
|
+
return {
|
|
528
|
+
robots: {
|
|
529
|
+
index: true, // Allow indexing
|
|
530
|
+
follow: true,
|
|
531
|
+
},
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## Styling
|
|
539
|
+
|
|
540
|
+
### Tailwind Typography
|
|
541
|
+
|
|
542
|
+
The framework uses `@tailwindcss/typography` for automatic markdown styling.
|
|
543
|
+
|
|
544
|
+
**Included styles:**
|
|
545
|
+
- Headings (h1-h6) with proper sizing and spacing
|
|
546
|
+
- Paragraphs with optimal line height
|
|
547
|
+
- Lists (ul, ol) with proper indentation
|
|
548
|
+
- Links with hover states
|
|
549
|
+
- Blockquotes with border and background
|
|
550
|
+
- Code blocks with syntax highlighting
|
|
551
|
+
- Tables with borders and alternating rows
|
|
552
|
+
|
|
553
|
+
### Customization
|
|
554
|
+
|
|
555
|
+
Override prose styles in PolicyLayout or globally:
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
<div className="prose prose-lg prose-primary">
|
|
559
|
+
{/* Custom prose theme */}
|
|
560
|
+
</div>
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
**Available modifiers:**
|
|
564
|
+
- `prose-sm`, `prose-base`, `prose-lg`, `prose-xl`, `prose-2xl` - Size
|
|
565
|
+
- `prose-gray`, `prose-blue`, `prose-green` - Color theme
|
|
566
|
+
- `prose-headings:text-primary` - Custom heading color
|
|
567
|
+
- `prose-a:text-blue-600` - Custom link color
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Advanced: MDX Components
|
|
572
|
+
|
|
573
|
+
Use `.mdx` extension to embed React components in markdown.
|
|
574
|
+
|
|
575
|
+
### Example MDX File
|
|
576
|
+
|
|
577
|
+
```mdx
|
|
578
|
+
---
|
|
579
|
+
title: "Privacy Policy"
|
|
580
|
+
lastUpdated: "2026-02-03"
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
import { Alert } from '@/components/ui/Alert';
|
|
584
|
+
import { Button } from '@/components/ui/Button';
|
|
585
|
+
|
|
586
|
+
# Privacy Policy
|
|
587
|
+
|
|
588
|
+
<Alert variant="info">
|
|
589
|
+
This policy was last updated on {frontmatter.lastUpdated}
|
|
590
|
+
</Alert>
|
|
591
|
+
|
|
592
|
+
## 1. Information We Collect
|
|
593
|
+
|
|
594
|
+
We collect the following types of information...
|
|
595
|
+
|
|
596
|
+
<Button href="/contact">Contact Us About Privacy</Button>
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### When to Use MDX
|
|
600
|
+
|
|
601
|
+
- Need to embed interactive components
|
|
602
|
+
- Want custom alerts or callouts
|
|
603
|
+
- Need tables with custom styling
|
|
604
|
+
- Want to include forms or calculators
|
|
605
|
+
- Need dynamic content based on frontmatter
|
|
606
|
+
|
|
607
|
+
### When to Use Plain Markdown
|
|
608
|
+
|
|
609
|
+
- Pure text content (most policies)
|
|
610
|
+
- Simplicity is important
|
|
611
|
+
- Non-technical editors will maintain content
|
|
612
|
+
- No interactive elements needed
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## Performance
|
|
617
|
+
|
|
618
|
+
### Static Generation (SSG)
|
|
619
|
+
|
|
620
|
+
Policy pages use Next.js static generation for optimal performance:
|
|
621
|
+
|
|
622
|
+
**Benefits:**
|
|
623
|
+
- ⚡ Instant page loads (served from CDN)
|
|
624
|
+
- 🔍 Perfect SEO (pre-rendered HTML)
|
|
625
|
+
- 💰 Low server costs (no compute per request)
|
|
626
|
+
- 📱 Excellent mobile performance
|
|
627
|
+
|
|
628
|
+
**Build time:**
|
|
629
|
+
```bash
|
|
630
|
+
npm run build
|
|
631
|
+
# Generates static HTML for all policy pages
|
|
632
|
+
# app/[locale]/privacy/page.tsx → /en/privacy/index.html
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Bundle Size
|
|
636
|
+
|
|
637
|
+
Markdown content is NOT included in JavaScript bundle:
|
|
638
|
+
|
|
639
|
+
- Content compiled to HTML at build time
|
|
640
|
+
- Only layout components in client bundle
|
|
641
|
+
- ~5-10KB additional JavaScript
|
|
642
|
+
- 0KB content overhead (it's in HTML)
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## Testing
|
|
647
|
+
|
|
648
|
+
### Test Checklist
|
|
649
|
+
|
|
650
|
+
- [ ] Markdown renders correctly
|
|
651
|
+
- [ ] Frontmatter parsed properly
|
|
652
|
+
- [ ] Table of contents generated
|
|
653
|
+
- [ ] TOC scroll spy works
|
|
654
|
+
- [ ] Smooth scrolling to sections
|
|
655
|
+
- [ ] Mobile responsive layout
|
|
656
|
+
- [ ] All locales load correctly
|
|
657
|
+
- [ ] SEO meta tags present
|
|
658
|
+
- [ ] Last updated date formatted correctly
|
|
659
|
+
- [ ] Contact link works
|
|
660
|
+
- [ ] Heading hierarchy is valid
|
|
661
|
+
- [ ] No broken links
|
|
662
|
+
- [ ] Code blocks render properly
|
|
663
|
+
- [ ] Tables display correctly
|
|
664
|
+
|
|
665
|
+
### Manual Testing
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
# Start dev server
|
|
669
|
+
npm run dev
|
|
670
|
+
|
|
671
|
+
# Visit policy pages
|
|
672
|
+
http://localhost:3000/en/privacy
|
|
673
|
+
http://localhost:3000/fr/privacy
|
|
674
|
+
|
|
675
|
+
# Check table of contents
|
|
676
|
+
# - Click TOC links → smooth scroll
|
|
677
|
+
# - Scroll page → TOC highlights active section
|
|
678
|
+
|
|
679
|
+
# Test mobile
|
|
680
|
+
# - TOC should hide on small screens
|
|
681
|
+
# - Content should be readable
|
|
682
|
+
# - No horizontal scroll
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
## Common Patterns
|
|
688
|
+
|
|
689
|
+
### Policy Index Page
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
// app/[locale]/policies/page.tsx
|
|
693
|
+
import { getAllPolicies } from 'simple-site-framework/lib/content';
|
|
694
|
+
|
|
695
|
+
export default async function PoliciesPage({ params }) {
|
|
696
|
+
const policies = await getAllPolicies(params.locale);
|
|
697
|
+
|
|
698
|
+
return (
|
|
699
|
+
<div>
|
|
700
|
+
<h1>Legal Documents</h1>
|
|
701
|
+
<ul>
|
|
702
|
+
{policies.map(policy => (
|
|
703
|
+
<li key={policy.slug}>
|
|
704
|
+
<Link href={`/${params.locale}/policies/${policy.slug}`}>
|
|
705
|
+
<h3>{policy.metadata.title}</h3>
|
|
706
|
+
<p>{policy.metadata.description}</p>
|
|
707
|
+
<time>Updated: {policy.metadata.lastUpdated}</time>
|
|
708
|
+
</Link>
|
|
709
|
+
</li>
|
|
710
|
+
))}
|
|
711
|
+
</ul>
|
|
712
|
+
</div>
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### Dynamic Policy Routes
|
|
718
|
+
|
|
719
|
+
```typescript
|
|
720
|
+
// app/[locale]/policies/[slug]/page.tsx
|
|
721
|
+
import { loadPolicy, getPolicySlugs } from 'simple-site-framework/lib/content';
|
|
722
|
+
import { PolicyLayout } from 'simple-site-framework';
|
|
723
|
+
import { notFound } from 'next/navigation';
|
|
724
|
+
|
|
725
|
+
export default async function PolicyPage({ params }) {
|
|
726
|
+
try {
|
|
727
|
+
const { content, metadata } = await loadPolicy(params.slug, params.locale);
|
|
728
|
+
|
|
729
|
+
return (
|
|
730
|
+
<PolicyLayout
|
|
731
|
+
title={metadata.title}
|
|
732
|
+
lastUpdated={metadata.lastUpdated}
|
|
733
|
+
locale={params.locale}
|
|
734
|
+
>
|
|
735
|
+
{content}
|
|
736
|
+
</PolicyLayout>
|
|
737
|
+
);
|
|
738
|
+
} catch {
|
|
739
|
+
notFound();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export async function generateStaticParams() {
|
|
744
|
+
const slugs = getPolicySlugs();
|
|
745
|
+
const locales = ['en', 'fr'];
|
|
746
|
+
|
|
747
|
+
return slugs.flatMap(slug =>
|
|
748
|
+
locales.map(locale => ({ slug, locale }))
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
---
|
|
754
|
+
|
|
755
|
+
## Best Practices
|
|
756
|
+
|
|
757
|
+
### Content
|
|
758
|
+
|
|
759
|
+
✅ **DO:**
|
|
760
|
+
- Write in clear, plain language
|
|
761
|
+
- Use descriptive section headings
|
|
762
|
+
- Include "Last updated" date
|
|
763
|
+
- Provide contact information
|
|
764
|
+
- Link to related policies
|
|
765
|
+
- Use bullet points for lists
|
|
766
|
+
- Keep paragraphs short (3-4 sentences)
|
|
767
|
+
|
|
768
|
+
❌ **DON'T:**
|
|
769
|
+
- Use overly legal jargon
|
|
770
|
+
- Create walls of text
|
|
771
|
+
- Skip heading levels
|
|
772
|
+
- Forget to update date when editing
|
|
773
|
+
- Mix multiple policies in one file
|
|
774
|
+
|
|
775
|
+
### Structure
|
|
776
|
+
|
|
777
|
+
✅ **DO:**
|
|
778
|
+
- Use consistent heading hierarchy
|
|
779
|
+
- Create logical sections
|
|
780
|
+
- Include table of contents
|
|
781
|
+
- Add introduction/overview
|
|
782
|
+
- Provide contact section at end
|
|
783
|
+
|
|
784
|
+
❌ **DON'T:**
|
|
785
|
+
- Use more than 3-4 heading levels
|
|
786
|
+
- Create sections with only one subsection
|
|
787
|
+
- Nest lists too deeply
|
|
788
|
+
|
|
789
|
+
### Maintenance
|
|
790
|
+
|
|
791
|
+
✅ **DO:**
|
|
792
|
+
- Version control all changes
|
|
793
|
+
- Review policies annually
|
|
794
|
+
- Update "lastUpdated" date
|
|
795
|
+
- Get legal review before publishing
|
|
796
|
+
- Test all locales after updates
|
|
797
|
+
|
|
798
|
+
❌ **DON'T:**
|
|
799
|
+
- Edit directly in production
|
|
800
|
+
- Forget to update translations
|
|
801
|
+
- Leave outdated information
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
## Troubleshooting
|
|
806
|
+
|
|
807
|
+
### "Policy file not found"
|
|
808
|
+
|
|
809
|
+
**Error:** `Policy file not found: privacy-policy.en.md`
|
|
810
|
+
|
|
811
|
+
**Solution:**
|
|
812
|
+
1. Check file exists: `src/content/policies/privacy-policy.en.md`
|
|
813
|
+
2. Verify filename format: `{slug}.{locale}.md`
|
|
814
|
+
3. Check file extension: `.md` or `.mdx`
|
|
815
|
+
4. Verify contentDir path if custom
|
|
816
|
+
|
|
817
|
+
### "Missing required frontmatter"
|
|
818
|
+
|
|
819
|
+
**Error:** `Policy privacy-policy.en.md is missing required frontmatter field: title`
|
|
820
|
+
|
|
821
|
+
**Solution:**
|
|
822
|
+
1. Add frontmatter at top of file:
|
|
823
|
+
```markdown
|
|
824
|
+
---
|
|
825
|
+
title: "Your Title"
|
|
826
|
+
lastUpdated: "2026-02-03"
|
|
827
|
+
---
|
|
828
|
+
```
|
|
829
|
+
2. Ensure proper YAML format (quotes, colons)
|
|
830
|
+
3. Check for typos in field names
|
|
831
|
+
|
|
832
|
+
### Table of Contents not showing
|
|
833
|
+
|
|
834
|
+
**Solutions:**
|
|
835
|
+
1. Ensure headings use proper markdown (`##`, `###`)
|
|
836
|
+
2. Check `showToc={true}` in PolicyLayout
|
|
837
|
+
3. Verify at least one H2 or H3 exists
|
|
838
|
+
4. Check `includeLevels` matches heading levels
|
|
839
|
+
|
|
840
|
+
### Styling not applied
|
|
841
|
+
|
|
842
|
+
**Solutions:**
|
|
843
|
+
1. Install Tailwind Typography: `npm install -D @tailwindcss/typography`
|
|
844
|
+
2. Add to tailwind.config.js: `plugins: [require('@tailwindcss/typography')]`
|
|
845
|
+
3. Restart dev server after config changes
|
|
846
|
+
|
|
847
|
+
---
|
|
848
|
+
|
|
849
|
+
## Examples
|
|
850
|
+
|
|
851
|
+
See complete examples in:
|
|
852
|
+
- `examples/policies/privacy-policy.en.md`
|
|
853
|
+
- `examples/policies/terms-of-service.en.md`
|
|
854
|
+
- `examples/app/[locale]/policies/` (page components)
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
## Resources
|
|
859
|
+
|
|
860
|
+
- [Tailwind Typography Docs](https://tailwindcss.com/docs/typography-plugin)
|
|
861
|
+
- [next-mdx-remote](https://github.com/hashicorp/next-mdx-remote)
|
|
862
|
+
- [Markdown Guide](https://www.markdownguide.org/)
|
|
863
|
+
- [YAML Frontmatter Spec](https://jekyllrb.com/docs/front-matter/)
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
**Questions?** Open an issue on GitHub or contact us.
|