Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Documentation

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test:llm
- run: pnpm build
27 changes: 18 additions & 9 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Overview

The Nativewind documentation site serves both v4 (stable) and v5 (preview) docs side-by-side. Built with Next.js 15 and Fumadocs.
The Nativewind documentation site serves both v4.2.7 (stable) and v5 (release candidate) documentation. Built with Next.js 15 and Fumadocs.

- **V4 docs** at `/docs/` — sourced from `content/docs/`
- **V5 docs** at `/v5/` — sourced from `content/v5/`
Expand All @@ -21,7 +21,8 @@ The Nativewind documentation site serves both v4 (stable) and v5 (preview) docs
```bash
pnpm install # Install dependencies
pnpm dev # Start dev server
pnpm build # Production build
pnpm test:llm # Verify all public Markdown exports and shared helpers
pnpm build # Production build, including text endpoints
pnpm start # Start production server
```

Expand Down Expand Up @@ -72,8 +73,8 @@ app/
├── docs/[[...slug]]/ # V4 doc pages
├── v5/[[...slug]]/ # V5 doc pages
├── llms.mdx/ # LLM-friendly markdown endpoints
├── llms.txt/ # Concatenated LLM text (v4)
├── v5/llms.txt/ # Concatenated LLM text (v5)
├── llms.txt/ # Documentation index (v4)
├── v5/llms.txt/ # Documentation index (v5)
└── api/ # Search, OG image generation

components/
Expand All @@ -89,7 +90,7 @@ content/

lib/
├── source.ts # Fumadocs loaders (source for v4, source5 for v5)
└── get-llm-text.ts # Strips frontmatter, formats docs for LLM endpoints
└── get-llm-text.ts # Formats source Markdown for LLM endpoints
```

## Key Config Files
Expand All @@ -104,12 +105,20 @@ The site has built-in endpoints for serving docs as plain text for LLM consumpti

| Endpoint | Description |
|----------|-------------|
| `/llms.txt` | Concatenated v4 docs as plain text |
| `/v5/llms.txt` | Concatenated v5 docs as plain text |
| `/llms.txt` | V4 documentation index with Markdown page links |
| `/v5/llms.txt` | V5 documentation index with Markdown page links |
| `/llms-full.txt` | Full v4 doc dump |
| `/v5/llms-full.txt` | Full v5 doc dump |
| `/llms.mdx/docs/[path]` | Individual v4 doc as markdown |
| `/llms.mdx/v5/[path]` | Individual v5 doc as markdown |
| `/llms.mdx/docs/[path]` | Individual v4 page as Markdown; also `/docs/[path].mdx` |
| `/llms.mdx/v5/[path]` | Individual v5 page as Markdown; also `/v5/[path].mdx` |

The indices, full exports and copy buttons all use the same documentation source. `lib/llm-markdown.ts` expands imported MDX partials and includes, renders tabs, callouts and tables as Markdown, preserves code fences and resolves page links. Files beginning with `_` are helpers and are not exported as standalone pages.

`lib/doc-tables.ts` supplies install commands and compatibility rows to both the React components and the text exporter. Update those shared helpers when changing command generation or support labels. Installation and migration copy buttons fetch the page Markdown endpoint; do not reintroduce separate guide strings.

The exporter supports the data expressions and components used by this repository. It deliberately fails on unknown components or JavaScript expressions instead of silently dropping guidance. When introducing a component, add its Markdown representation and a regression case to `tests/llm-markdown.test.ts`. Run `pnpm test:llm` and `pnpm build`; check rendered pages and copy buttons if UI components changed. Legacy `.md` files are included in those checks.

Keep v4 and v5 installation advice separate. Use the pinned pair from the RC installation page for v5; do not copy current Tailwind CSS v4 links into the stable Tailwind CSS v3 docs. The application migration skills are maintained in [nativewind/nativewind](https://github.com/nativewind/nativewind/tree/main/skills), alongside release setup and measured compatibility records.

## Common Pitfalls

Expand Down
13 changes: 8 additions & 5 deletions app/llms-full.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { source } from '@/lib/source';
import { getLLMText } from '@/lib/get-llm-text';
import { getLLMText, isPublicDoc } from '@/lib/get-llm-text';

export const revalidate = false;

export async function GET() {
const scan = source.getPages().map(getLLMText);
const scanned = await Promise.all(scan);

return new Response(scanned.join('\n\n'));
const pages = source.getPages().filter((page) => isPublicDoc(page.file.path));
const texts = await Promise.all(
pages.map((page) => getLLMText(page, 'docs')),
);
return new Response(texts.join('\n\n'), {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
16 changes: 8 additions & 8 deletions app/llms.mdx/docs/[[...slug]]/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getLLMText } from '@/lib/get-llm-text';
import { getLLMText, isPublicDoc } from '@/lib/get-llm-text';
import { source } from '@/lib/source';
import { notFound } from 'next/navigation';

Expand All @@ -10,15 +10,15 @@ export async function GET(
) {
const { slug } = await params;
const page = source.getPage(slug);
if (!page) notFound();

return new Response(await getLLMText(page), {
headers: {
'Content-Type': 'text/markdown',
},
if (!page || !isPublicDoc(page.file.path)) notFound();
return new Response(await getLLMText(page, 'docs'), {
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
});
}

export function generateStaticParams() {
return source.generateParams();
return source
.getPages()
.filter((page) => isPublicDoc(page.file.path))
.map((page) => ({ slug: page.slugs }));
}
28 changes: 8 additions & 20 deletions app/llms.mdx/v5/[[...slug]]/route.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import { getLLMText, isPublicDoc } from '@/lib/get-llm-text';
import { source5 } from '@/lib/source';
import { notFound } from 'next/navigation';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';

export const revalidate = false;

function stripFrontmatter(content: string): string {
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
return match ? content.slice(match[0].length).trim() : content.trim();
}

export async function GET(
_req: Request,
{ params }: { params: Promise<{ slug?: string[] }> },
) {
const { slug } = await params;
const page = source5.getPage(slug);
if (!page) notFound();

const filePath = join(process.cwd(), 'content/v5', page.file.path);
const raw = await readFile(filePath, 'utf-8');
const body = stripFrontmatter(raw);

const text = `# ${page.data.title} (${page.url})\n\n${page.data.description ? `${page.data.description}\n\n` : ''}${body}`;

return new Response(text, {
headers: {
'Content-Type': 'text/markdown',
},
if (!page || !isPublicDoc(page.file.path)) notFound();
return new Response(await getLLMText(page, 'v5'), {
headers: { 'Content-Type': 'text/markdown; charset=utf-8' },
});
}

export function generateStaticParams() {
return source5.generateParams();
return source5
.getPages()
.filter((page) => isPublicDoc(page.file.path))
.map((page) => ({ slug: page.slugs }));
}
73 changes: 2 additions & 71 deletions app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,10 @@
import { source } from '@/lib/source';
import { getLLMIndex } from '@/lib/llm-index';

export const revalidate = false;

const BASE_URL = 'https://nativewind.dev';

interface Section {
title: string;
pages: { title: string; url: string; description?: string }[];
}

export async function GET() {
const pages = source.getPages();

const sectionMap: Record<string, Section> = {};
const sectionOrder = [
{ prefix: '', title: 'Overview' },
{ prefix: 'getting-started', title: 'Getting Started' },
{ prefix: 'guides', title: 'Guides' },
{ prefix: 'core-concepts', title: 'Core Concepts' },
{ prefix: 'customization', title: 'Customization' },
{ prefix: 'api', title: 'API' },
{ prefix: 'tailwind', title: 'Tailwind CSS Utilities' },
];

for (const section of sectionOrder) {
sectionMap[section.prefix] = { title: section.title, pages: [] };
}

for (const page of pages) {
const slugs = page.slugs;
const firstSegment = slugs[0] ?? '';

let sectionKey: string;
if (slugs.length === 0) {
sectionKey = '';
} else if (firstSegment in sectionMap) {
sectionKey = firstSegment;
} else {
sectionKey = 'tailwind';
}

sectionMap[sectionKey]?.pages.push({
title: page.data.title,
url: `${BASE_URL}${page.url}`,
description: page.data.description,
});
}

const lines: string[] = [
'# NativeWind v4',
'',
'> NativeWind uses Tailwind CSS as a scripting language to create a universal style system for React Native. It compiles Tailwind CSS styles into native StyleSheet objects at build time while providing an efficient runtime for conditional styles like hover, focus, media queries, and container queries.',
'',
];

for (const section of sectionOrder) {
const data = sectionMap[section.prefix];
if (!data || data.pages.length === 0) continue;

lines.push(`## ${data.title}`, '');
for (const page of data.pages) {
const desc = page.description ? `: ${page.description}` : '';
lines.push(`- [${page.title}](${page.url})${desc}`);
}
lines.push('');
}

lines.push(
'## Optional',
'',
`- [Full documentation](${BASE_URL}/llms-full.txt): Complete NativeWind v4 documentation in a single file for LLM consumption`,
'',
);

return new Response(lines.join('\n'), {
return new Response(getLLMIndex(source.getPages(), 'docs'), {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
24 changes: 5 additions & 19 deletions app/v5/llms-full.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import { source5 } from '@/lib/source';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { getLLMText, isPublicDoc } from '@/lib/get-llm-text';

export const revalidate = false;

function stripFrontmatter(content: string): string {
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
return match ? content.slice(match[0].length).trim() : content.trim();
}

export async function GET() {
const pages = source5.getPages();

const texts = await Promise.all(
pages.map(async (page) => {
const filePath = join(process.cwd(), 'content/v5', page.file.path);
const raw = await readFile(filePath, 'utf-8');
const body = stripFrontmatter(raw);

return `# ${page.data.title} (${page.url})\n\n${page.data.description ? `${page.data.description}\n\n` : ''}${body}`;
}),
);

const pages = source5
.getPages()
.filter((page) => isPublicDoc(page.file.path));
const texts = await Promise.all(pages.map((page) => getLLMText(page, 'v5')));
return new Response(texts.join('\n\n'), {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
Expand Down
75 changes: 2 additions & 73 deletions app/v5/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,10 @@
import { source5 } from '@/lib/source';
import { getLLMIndex } from '@/lib/llm-index';

export const revalidate = false;

const BASE_URL = 'https://nativewind.dev';

interface Section {
title: string;
pages: { title: string; url: string; description?: string }[];
}

export async function GET() {
const pages = source5.getPages();

const sectionMap: Record<string, Section> = {};
const sectionOrder = [
{ prefix: '', title: 'Overview' },
{ prefix: 'getting-started', title: 'Getting Started' },
{ prefix: 'guides', title: 'Guides' },
{ prefix: 'core-concepts', title: 'Core Concepts' },
{ prefix: 'customization', title: 'Customization' },
{ prefix: 'api', title: 'API' },
{ prefix: 'tailwind', title: 'Tailwind CSS Utilities' },
];

for (const section of sectionOrder) {
sectionMap[section.prefix] = { title: section.title, pages: [] };
}

for (const page of pages) {
const slugs = page.slugs;
const firstSegment = slugs[0] ?? '';

let sectionKey: string;
if (slugs.length === 0) {
sectionKey = '';
} else if (firstSegment in sectionMap) {
sectionKey = firstSegment;
} else {
sectionKey = 'tailwind';
}

sectionMap[sectionKey]?.pages.push({
title: page.data.title,
url: `${BASE_URL}${page.url}`,
description: page.data.description,
});
}

const lines: string[] = [
'# Nativewind v5 RC',
'',
'Target: nativewind@5.0.0-rc.0 and react-native-css@3.1.0-rc.0 on the tested Expo 57 toolchain. V4.2.7 remains the stable release.',
'',
'> Nativewind v5 uses Tailwind CSS v4 as a scripting language to create a universal style system for React Native. Built on top of react-native-css, it compiles Tailwind CSS styles into native StyleSheet objects at build time while providing an efficient runtime for conditional styles like hover, focus, media queries, and container queries.',
'',
];

for (const section of sectionOrder) {
const data = sectionMap[section.prefix];
if (!data || data.pages.length === 0) continue;

lines.push(`## ${data.title}`, '');
for (const page of data.pages) {
const desc = page.description ? `: ${page.description}` : '';
lines.push(`- [${page.title}](${page.url})${desc}`);
}
lines.push('');
}

lines.push(
'## Optional',
'',
`- [Full documentation](${BASE_URL}/v5/llms-full.txt): Complete Nativewind v5 documentation in a single file for LLM consumption`,
'',
);

return new Response(lines.join('\n'), {
return new Response(getLLMIndex(source5.getPages(), 'v5'), {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
Loading
Loading