persian-fontkit
v1.0.2
Published
Automatic Persian (Farsi) web font optimizer for Next.js and React with smart caching, subsetting, preloading, and SSR support
Downloads
13
Maintainers
Readme
🎨 Persian FontKit
Automatic Persian (Farsi) web font optimizer for Next.js and React
Reduce font file sizes by 60-80% • Improve Core Web Vitals • Zero Config
Features • Installation • Usage • Examples • API
🧩 Problem this Solves
Persian fonts like Vazir, IRANSans, Shabnam, Yekan, and Kalameh are typically 2-4 MB in size, causing:
- ⏱️ Slow page load times
- 📉 Poor Core Web Vitals scores
- 💸 Higher bandwidth costs
- 😞 Bad user experience
persian-fontkit automatically:
✅ Subsets unused glyphs (60-80% size reduction)
✅ Generates optimized .woff2 files
✅ Creates CSS with font-display: swap
✅ Adds preload tags for faster loading
✅ Provides React hooks for dynamic loading
✅ Integrates seamlessly with Next.js
⚡ Features
🔧 CLI Tool
- Optimize entire font directories with one command
- Support for
.ttf,.otf,.woff,.woff2formats - Automatic subset generation (Persian + Latin + Numbers)
- Hash-based cache-busting filenames
- Smart caching system - Skip re-optimization of unchanged fonts (~100x faster)
- Beautiful CLI output with progress tracking
⚛️ React Integration
usePersianFonthook for client-side dynamic font loading- SSR-compatible (doesn't break server rendering)
- Automatic
<link rel="preload">injection (client-side) - TypeScript support with full type definitions
- Note: For true SSR, use static preload in
layout.tsx(see below)
🚀 Next.js Plugin
- Automatic font optimization during build
- Zero-config setup for Next.js 13, 14, and 15
- App Router and Pages Router support
- Works with
next/font/localpatterns
📦 Installation
npm install persian-fontkit
# or
yarn add persian-fontkit
# or
pnpm add persian-fontkit🚀 Quick Start
1. CLI Usage
Optimize all fonts in a directory:
npx persian-fontkit optimize ./public/fonts --output ./dist/fontsOutput:
🚀 Persian FontKit Optimizer
Input: /path/to/public/fonts
Output: /path/to/dist/fonts
📁 Scanning for font files...
✓ Found 2 font file(s)
[1/2] Optimizing vazir-regular.ttf...
Original: 2.4 MB
Optimized: 580 KB
✓ Reduced by 75.8%
[2/2] Optimizing vazir-bold.ttf...
Original: 2.5 MB
Optimized: 620 KB
✓ Reduced by 75.2%
✨ Optimization complete!
📊 Summary:
Total fonts processed: 2
Original size: 4.9 MB
Optimized size: 1.2 MB
Total saved: 3.7 MB
Size reduction: 75.5%2. React Hook Usage (Client-Side)
"use client"; // ⚠️ Required: Hook only works in client components
import { usePersianFont } from "persian-fontkit/hooks";
export default function MyComponent() {
// Loads fonts dynamically after page hydration
usePersianFont({
family: "Vazir",
weight: [400, 700],
subsets: ["farsi", "latin"],
basePath: "/fonts",
});
return (
<div style={{ fontFamily: "Vazir" }}>
<h1>سلام دنیا</h1>
<p>Hello World</p>
</div>
);
}Note: This hook runs client-side only (using
useEffect). For SSR, see Next.js Integration.
3. Next.js Integration
Option A: Static Preload (Recommended for SSR) ✨
For true server-side rendering with optimal performance:
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="fa" dir="rtl">
<head>
{/* ✅ This runs on the server - true SSR */}
<link
rel="preload"
href="/fonts/vazir-400.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
<style>{`
@font-face {
font-family: 'Vazir';
src: url('/fonts/vazir-400.woff2') format('woff2');
font-display: swap;
}
`}</style>
</head>
<body>{children}</body>
</html>
);
}Option B: Client-Side Hook (For Dynamic Loading) ⚡
For client-side dynamic font loading (after page hydration):
"use client";
import { usePersianFont } from "persian-fontkit/hooks";
export default function MyComponent() {
// ⚠️ This runs only in the browser (useEffect)
usePersianFont({
family: "Vazir",
weight: [400, 700],
basePath: "/fonts",
});
return <div style={{ fontFamily: "Vazir" }}>محتوای شما</div>;
}Option C: Next.js Plugin (Build-Time Optimization)
// next.config.js
const { withPersianFonts } = require("persian-fontkit/plugin");
module.exports = withPersianFonts({
fonts: [{ family: "Vazir", weights: [400, 700], subsets: ["farsi"] }],
});� SSR vs Client-Side: Which Should I Use?
✅ Use Static Preload (Option A) when:
- You want true server-side rendering
- First paint performance is critical
- You know your fonts at build time
- SEO and Core Web Vitals are important
- Recommended for production apps
⚡ Use Client-Side Hook (Option B) when:
- You need dynamic font loading based on user preferences
- Loading fonts conditionally (e.g., language switcher)
- Building interactive demos or playgrounds
- Font selection happens at runtime
🎯 Technical Details:
| Feature | Static Preload | Client Hook | | --------------- | ------------------ | ------------------- | | Execution | Server-side | Client-side only | | Load Time | Before first paint | After hydration | | SSR Support | ✅ True SSR | ⚠️ SSR-compatible* | | Performance | Faster FCP/LCP | Slower initial load | | Use Case | Production apps | Dynamic scenarios |
*SSR-compatible means it won't break during server rendering (safe window check), but actual font loading happens only in the browser.
�📚 Usage Examples
CLI Options
# Basic usage
npx persian-fontkit optimize ./fonts --output ./dist
# Custom subsets
npx persian-fontkit optimize ./fonts --subsets farsi latin numbers
# Different format
npx persian-fontkit optimize ./fonts --format woff
# Disable hash in filenames
npx persian-fontkit optimize ./fonts --no-hash
# Custom CSS filename
npx persian-fontkit optimize ./fonts --css custom-fonts.css
# Disable caching (re-optimize all fonts)
npx persian-fontkit optimize ./fonts --no-cache
# Custom cache directory
npx persian-fontkit optimize ./fonts --cache-dir ./.my-cache
# View cache statistics
npx persian-fontkit cache --stats
# Clear cache
npx persian-fontkit cache --clear
# Show help
npx persian-fontkit --helpReact Hook Options
import { usePersianFont } from "persian-fontkit/hooks";
function MyComponent() {
usePersianFont({
family: "Vazir", // Font family name
weight: [400, 700], // Font weights to load
subsets: ["farsi", "latin"], // Character subsets
basePath: "/fonts", // Base path for font files
display: "swap", // font-display property
preload: true, // Add preload links
fallback: ["Tahoma", "Arial"], // Fallback fonts
});
return <div>Your content</div>;
}Programmatic API
import { optimizeFont, optimizeFonts, getGlobalCache } from "persian-fontkit";
// Optimize a single font (cache enabled by default)
const result = await optimizeFont({
inputPath: "./fonts/vazir-regular.ttf",
outputDir: "./dist/fonts",
fontFamily: "Vazir",
fontWeight: 400,
subsets: ["farsi", "latin"],
format: "woff2",
cache: true, // Default: true
});
console.log(`Reduced by ${result.reduction}%`);
// Disable cache for specific optimization
const result2 = await optimizeFont({
inputPath: "./fonts/special.ttf",
outputDir: "./dist/fonts",
cache: false, // Disable caching
});
// Optimize multiple fonts
const results = await optimizeFonts(
["./fonts/vazir-400.ttf", "./fonts/vazir-700.ttf"],
"./dist/fonts",
{ subsets: ["farsi", "latin"] }
);
// Cache management
const cache = getGlobalCache();
// Get cache statistics
const stats = await cache.getStats();
console.log(`Cache entries: ${stats.entries}`);
console.log(`Total size: ${stats.totalSize} bytes`);
// Clear cache
await cache.clear();
// Clean old entries (older than 7 days)
const deletedCount = await cache.cleanOld(7 * 24 * 60 * 60 * 1000);
console.log(`Deleted ${deletedCount} old entries`);🎯 Supported Subsets
| Subset | Unicode Range | Description |
| ------------- | ------------------------ | --------------------------- |
| farsi | U+0600-06FF, U+FB50-FDFF | Persian & Arabic characters |
| latin | U+0020-007E, U+00A0-00FF | Basic Latin characters |
| numbers | U+0030-0039, U+06F0-06F9 | Latin & Persian digits |
| punctuation | U+0020-002F, U+060C-061F | Common punctuation |
Default: All subsets are included by default.
🎨 Popular Persian Fonts
Persian FontKit works with all Persian fonts, including:
- Vazir (Vazirmatn)
- IRANSans (IRANSansX)
- Shabnam
- Yekan
- Kalameh
- Samim
- Sahel
- Tanha
- Parastoo
- Gandom
📊 Performance Comparison
Before Optimization
vazir-regular.ttf: 2.4 MB
vazir-bold.ttf: 2.5 MB
Total: 4.9 MB
Load time: ~3.2s (3G)After Optimization
vazir-regular.woff2: 580 KB ⚡
vazir-bold.woff2: 620 KB ⚡
Total: 1.2 MB
Load time: ~0.8s (3G)Result: 75% smaller, 4x faster! 🚀
🔧 Configuration
CLI Config File
Create persian-fonts.config.js:
module.exports = {
fonts: [
{
family: "Vazir",
weights: [400, 700],
subsets: ["farsi", "latin", "numbers"],
},
{
family: "IRANSans",
weights: [400, 500, 700],
subsets: ["farsi"],
src: "iransans/*.ttf", // Optional: specific files
},
],
sourceDir: "./public/fonts",
outputDir: "./public/fonts/optimized",
format: "woff2",
verbose: true,
};Then run:
npx persian-fontkit optimize📖 API Reference
optimizeFont(options)
Optimize a single font file.
Options:
inputPath(string): Path to input font fileoutputDir(string): Output directoryfontFamily(string, optional): Font family namefontWeight(number, optional): Font weight (default: 400)fontStyle('normal' | 'italic', optional): Font stylesubsets(string[], optional): Character subsets to includeformat('woff2' | 'woff' | 'ttf', optional): Output formatfontDisplay(string, optional): CSS font-display propertyuseHash(boolean, optional): Add hash to filename
Returns: Promise<OptimizationResult>
usePersianFont(options)
React hook for client-side dynamic font loading.
⚠️ Important: This hook uses
useEffectand only runs in the browser (after hydration). It won't execute during server-side rendering. For true SSR, use static<link>tags in your layout component.
Options:
family(string): Font family nameweight(number | number[]): Font weightssubsets(string[], optional): Character subsetsbasePath(string, optional): Base path for fontsdisplay(string, optional): font-display propertypreload(boolean, optional): Add preload links (client-side)fallback(string[], optional): Fallback fonts
withPersianFonts(config, nextConfig)
Next.js plugin wrapper.
Config:
fonts(FontConfig[]): Array of font configurationssourceDir(string, optional): Source directoryoutputDir(string, optional): Output directoryformat(string, optional): Output formatverbose(boolean, optional): Enable logging
🏗️ Project Structure
persian-fontkit/
├── src/
│ ├── cli.ts # CLI implementation
│ ├── optimizer.ts # Core optimization logic
│ ├── next-plugin.ts # Next.js plugin
│ ├── index.ts # Main entry point
│ ├── hooks/
│ │ └── usePersianFont.ts # React hook
│ └── utils/
│ ├── css.ts # CSS generation
│ ├── file.ts # File operations
│ └── subsetConfig.ts # Subset definitions
├── examples/
│ └── nextjs-demo/ # Next.js example
├── package.json
├── tsconfig.json
└── README.md🤝 Contributing
Contributions are welcome! Please read our Contributing Guide first.
Development Setup
# Clone repository
git clone https://github.com/kuomars110/persian-fontkit.git
cd persian-fontkit
# Install dependencies
npm install
# Build
npm run build
# Watch mode
npm run dev📝 License
MIT © Your Name
🙏 Acknowledgments
- subset-font - Font subsetting library
- Commander.js - CLI framework
- Persian font designers for creating beautiful fonts
📮 Support
Made with ❤️ for the Persian web community
