This commit is contained in:
Ruben 2025-11-02 13:46:47 +01:00
parent b97b2f5503
commit ad516600bb
14 changed files with 6093 additions and 0 deletions

View file

@ -0,0 +1,739 @@
# Architecture
Understanding how FolderWeb works under the hood.
## High-Level Overview
FolderWeb follows a simple request-response flow:
```
HTTP Request
router.php (entry point)
Parse request path
Find content files
Determine content type (page/list/file)
Render content
Wrap in templates
HTTP Response
```
## Core Components
### 1. Router (`app/router.php`)
**Purpose**: Entry point for all requests, determines what to serve
**Responsibilities**:
- Receive HTTP requests
- Check for root-level assets (`/custom/assets/`)
- Parse request path
- Dispatch to appropriate renderer
- Handle redirects (trailing slashes)
- Serve 404 for missing content
**Key Flow**:
```php
// 1. Check for root-level assets
if (file_exists("/custom/assets/$path")) {
serve_static_file();
}
// 2. Empty path = home page (render all root content files)
if (empty($path)) {
render_all_files_in_root();
}
// 3. Parse request path
$result = parseRequestPath($ctx);
// 4. Handle based on type
match($result['type']) {
'page' => renderMultipleFiles(...),
'file' => renderFile(...),
'directory' => renderListView(...),
'not_found' => show_404()
};
```
**Location**: `/app/router.php` (lines 1-100+)
### 2. Content Discovery (`app/content.php`)
**Purpose**: Find and parse content files and directories
**Key Functions**:
#### `parseRequestPath($ctx)`
Analyzes request path and determines content type.
**Returns**:
```php
[
'type' => 'page' | 'file' | 'directory' | 'not_found',
'path' => '/full/system/path',
'files' => [...], // For page type
// ... other data
]
```
**Logic**:
1. Resolve translated slugs to real paths
2. Check if path exists
3. If directory:
- Has subdirectories? → `type: 'directory'` (list view)
- Has content files only? → `type: 'page'` (multi-file)
4. If matches file? → `type: 'file'`
5. Otherwise → `type: 'not_found'`
#### `findAllContentFiles($dir, $lang, $defaultLang, $availableLangs)`
Scans directory for content files.
**Process**:
1. Read directory contents
2. Filter for valid extensions (`.md`, `.html`, `.php`)
3. Parse filenames for language suffix
4. Filter by current language:
- Show `.{lang}.ext` files for that language
- Show default files (no suffix) only if no language variant
5. Sort alphanumerically
6. Return array of file paths
**Example**:
```php
// Directory contains:
// - index.md
// - index.no.md
// - about.md
// English request (lang=en, default=en):
findAllContentFiles() → ['index.md', 'about.md']
// Norwegian request (lang=no):
findAllContentFiles() → ['index.no.md', 'about.md']
```
#### `loadMetadata($dirPath, $lang, $defaultLang)`
Loads and merges metadata for a directory.
**Process**:
1. Check for `metadata.ini` in directory
2. Parse INI file with sections
3. Start with base values
4. Override with language-specific section if exists
5. Return merged array
**Example**:
```ini
title = "About"
summary = "Learn about us"
[no]
title = "Om"
summary = "Lær om oss"
```
For Norwegian request:
```php
loadMetadata(..., 'no', 'en') → [
'title' => 'Om', // Overridden
'summary' => 'Lær om oss' // Overridden
]
```
#### `resolveTranslatedPath($ctx, $requestPath)`
Maps translated slugs back to real directory names.
**Example**:
```ini
; In content/about/metadata.ini:
[no]
slug = "om-oss"
```
Request to `/no/om-oss/` resolves to `content/about/`.
**Process**:
1. Split path into segments
2. For each segment:
- Load metadata of parent directory
- Check if any subdirectory has matching translated slug
- Replace segment with real directory name
3. Return resolved path
### 3. Rendering Engine (`app/rendering.php`)
**Purpose**: Convert content to HTML and wrap in templates
**Key Functions**:
#### `renderContentFile($filePath)`
Converts a single content file to HTML.
**Process**:
```php
switch (extension) {
case 'md':
return Parsedown->text(file_contents);
case 'html':
return file_contents;
case 'php':
ob_start();
include $filePath; // $ctx available
return ob_get_clean();
}
```
#### `renderFile($ctx, $filePath)`
Renders single file wrapped in templates.
**Process**:
1. Convert file to HTML
2. Load metadata
3. Wrap in page template
4. Wrap in base template
5. Return HTML
#### `renderMultipleFiles($ctx, $filePaths, $pageDir)`
Renders multiple files as single page.
**Process**:
1. Convert each file to HTML
2. Concatenate HTML (in order)
3. Load metadata
4. Wrap in page template
5. Wrap in base template
6. Return HTML
**Used for**: Multi-file pages (documentation, long articles)
#### `renderTemplate($ctx, $content, $statusCode = 200)`
Wraps content in base template.
**Process**:
1. Extract variables for template
2. Set HTTP status code
3. Include base template
4. Return HTML
**Variables provided**:
- `$content` - Rendered HTML
- `$ctx` - Context object
- `$currentLang`, `$navigation`, `$homeLabel`, etc.
### 4. Context Object (`app/context.php`)
**Purpose**: Immutable request context with computed properties
**Implementation**:
```php
readonly class Context {
public function __construct(
public private(set) string $contentDir,
public private(set) string $currentLang,
// ... other properties
) {}
// Computed property (PHP 8.4 hook)
public string $langPrefix {
get => $this->currentLang !== $this->defaultLang
? "/{$this->currentLang}"
: '';
}
// Lazy-loaded computed property
public array $navigation {
get => buildNavigation($this);
}
}
```
**Benefits**:
- **Immutability**: Cannot be changed after creation
- **Type safety**: All properties typed
- **Computed values**: Calculated on-demand
- **No globals**: Passed explicitly
**Creation**:
```php
$ctx = createContext();
```
This function:
1. Loads configuration
2. Extracts language from URL
3. Determines content directory
4. Resolves template paths
5. Returns readonly Context object
### 5. Configuration (`app/config.php`)
**Purpose**: Load and merge configuration
**Process**:
1. Parse `/app/config.ini` (defaults)
2. Parse `/custom/config.ini` if exists
3. Merge arrays (custom overrides defaults)
4. Extract language settings
5. Validate configuration
**Configuration Used**:
```ini
[languages]
default = "en"
available = "en,no,fr"
```
### 6. Helper Functions (`app/helpers.php`)
**Purpose**: Utility functions used throughout
**Key Helpers**:
| Function | Purpose |
|----------|---------|
| `resolveTemplate($name, $type)` | Find custom or default template |
| `getSubdirectories($dir)` | List subdirectories only |
| `extractTitle($filePath, $lang, $defaultLang)` | Extract H1 from content |
| `formatNorwegianDate($date)` | Format date as "2. november 2025" |
| `extractDateFromFolder($name)` | Parse date from folder name |
| `findCoverImage($dir)` | Locate cover image |
| `findPdfFile($dir)` | Find first PDF |
### 7. Static File Server (`app/static.php`)
**Purpose**: Serve CSS, fonts, and other static assets
**Process**:
1. Validate path (prevent directory traversal)
2. Resolve real path
3. Check file exists and is readable
4. Determine MIME type
5. Set headers
6. Output file contents
**Routes**:
- `/app/styles/base.css` → Custom or default CSS
- `/app/default-styles/base.css` → Default CSS
- `/custom/fonts/*` → Custom fonts
## Data Flow
### Request Flow Diagram
```
1. HTTP Request: /blog/2025-11-02-post/
2. router.php receives request
3. createContext()
├─ Load config
├─ Extract language from URL
├─ Determine content directory
└─ Return Context object
4. parseRequestPath($ctx)
├─ resolveTranslatedPath() - map slug to real path
├─ Check path exists
├─ findAllContentFiles() - scan for content
└─ Return ['type' => 'file', 'path' => '...']
5. renderFile($ctx, $filePath)
├─ renderContentFile() - convert to HTML
├─ loadMetadata() - get metadata
├─ Apply page template
└─ Apply base template
6. HTTP Response: HTML
```
### List View Flow
```
1. Request: /blog/
2. parseRequestPath() → type: 'directory'
3. Load directory metadata
├─ Get page_template setting
└─ Get other directory metadata
4. getSubdirectories() - find all subdirs
5. For each subdirectory:
├─ loadMetadata() - get title, date, summary
├─ findCoverImage() - locate cover
├─ findPdfFile() - locate PDF
└─ Build item array
6. Render list template with $items
7. Wrap in base template
8. Return HTML
```
## File Organization
### Separation of Concerns
```
app/
├── router.php # Entry point, request handling
├── content.php # Content discovery, parsing
├── rendering.php # HTML generation, templates
├── context.php # Request context
├── config.php # Configuration loading
├── helpers.php # Utility functions
├── constants.php # Constants (extensions)
└── static.php # Static file serving
```
Each file has a single responsibility.
### Template Resolution
Templates use fallback chain:
```
1. /custom/templates/{name}.php
↓ (if not found)
2. /app/default/templates/{name}.php
```
**Implementation**:
```php
function resolveTemplate($name, $type = 'templates') {
$custom = __DIR__ . "/../custom/$type/$name";
$default = __DIR__ . "/default/$type/$name";
return file_exists($custom) ? $custom : $default;
}
```
This pattern applies to:
- Templates
- Styles
- Languages
- Any overridable resource
## Language Handling
### URL Structure
```
/ → Default language
/no/ → Norwegian
/fr/page/ → French page
```
### Language Extraction
From URL path:
```php
// Request: /no/blog/post/
$segments = explode('/', trim($path, '/'));
$firstSegment = $segments[0] ?? '';
if (in_array($firstSegment, $availableLangs)) {
$currentLang = $firstSegment;
$pathWithoutLang = implode('/', array_slice($segments, 1));
} else {
$currentLang = $defaultLang;
$pathWithoutLang = $path;
}
```
### Content Filtering
Files with language suffixes (`.{lang}.ext`) are filtered:
```php
// Parse filename
$parts = explode('.', $filename);
$lastPart = $parts[count($parts) - 2] ?? null;
// Check if second-to-last part is a language
if (in_array($lastPart, $availableLangs)) {
$fileLang = $lastPart;
} else {
$fileLang = $defaultLang;
}
// Include file if:
// - It matches current language, OR
// - It's default language AND no specific variant exists
```
## Navigation Building
### Process
```php
function buildNavigation($ctx) {
$items = [];
// 1. Scan content root for directories
$dirs = getSubdirectories($ctx->contentDir);
// 2. For each directory
foreach ($dirs as $dir) {
// Load metadata
$metadata = loadMetadata($dir, $ctx->currentLang, $ctx->defaultLang);
// Skip if menu = false
if (!($metadata['menu'] ?? false)) continue;
// Build item
$items[] = [
'title' => $metadata['title'] ?? basename($dir),
'url' => $ctx->langPrefix . '/' . basename($dir) . '/',
'order' => $metadata['menu_order'] ?? 999
];
}
// 3. Sort by menu_order
usort($items, fn($a, $b) => $a['order'] <=> $b['order']);
return $items;
}
```
### Caching
Navigation is a computed property, calculated once per request:
```php
public array $navigation {
get => buildNavigation($this);
}
```
PHP memoizes the result automatically.
## Performance Characteristics
### Time Complexity
| Operation | Complexity | Notes |
|-----------|------------|-------|
| Route resolution | O(1) | Direct file checks |
| Content file scan | O(n) | n = files in directory |
| Metadata loading | O(1) | Single file read |
| Template rendering | O(m) | m = content size |
| Navigation build | O(d) | d = top-level directories |
### Space Complexity
- **Memory**: O(c) where c = content size
- **No caching**: Each request independent
- **Stateless**: No session storage
### Optimization Points
1. **OPcache**: PHP bytecode caching (biggest impact)
2. **Web server cache**: Serve cached HTML
3. **Reverse proxy**: Varnish, Cloudflare
4. **Minimize file reads**: Context created once per request
## Security Architecture
### Path Validation
Multiple layers prevent directory traversal:
```php
// 1. Remove .. segments
$path = str_replace('..', '', $path);
// 2. Resolve to real path
$realPath = realpath($path);
// 3. Ensure within content directory
if (!str_starts_with($realPath, $contentDir)) {
return 404;
}
// 4. Check readable
if (!is_readable($realPath)) {
return 404;
}
```
### Output Escaping
All user-generated content escaped:
```php
<?= htmlspecialchars($metadata['title']) ?>
```
This prevents XSS attacks.
### MIME Type Validation
Static files served with correct MIME types:
```php
$mimeTypes = [
'css' => 'text/css',
'woff2' => 'font/woff2',
'jpg' => 'image/jpeg',
// ...
];
header('Content-Type: ' . $mimeTypes[$extension]);
```
## Error Handling
### HTTP Status Codes
- **200 OK**: Successful content render
- **301 Moved Permanently**: Missing trailing slash
- **404 Not Found**: Content doesn't exist
### 404 Handling
When content not found:
```php
renderTemplate($ctx, '<h1>404 Not Found</h1>', 404);
```
Base template rendered with 404 status.
## Extension Points
### Custom Templates
Override any template:
```php
// Framework checks:
$custom = '/custom/templates/list-my-custom.php';
if (file_exists($custom)) {
include $custom;
} else {
include '/app/default/templates/list.php';
}
```
### Custom Functions
Add your own in `/custom/functions.php`:
```php
<?php
// Custom helper functions
function myCustomFunction() {
// Your code
}
```
Include in router:
```php
if (file_exists(__DIR__ . '/../custom/functions.php')) {
require_once __DIR__ . '/../custom/functions.php';
}
```
### Content Files as PHP
`.php` content files have full access:
```php
<?php
// In content/dynamic/index.php
$currentTime = date('Y-m-d H:i:s');
?>
# Dynamic Content
Current time: <?= $currentTime ?>
The language is: <?= $ctx->currentLang ?>
```
## Testing Architecture
### Manual Testing
1. Create test content
2. Start dev server: `php -S localhost:8000 -t . app/router.php`
3. Visit URLs, verify output
4. Check different languages
5. Test edge cases (missing files, invalid paths)
### Automated Testing (Future)
Possible test structure:
```php
// tests/RouterTest.php
test('renders home page', function() {
$response = request('/');
expect($response->status)->toBe(200);
expect($response->body)->toContain('<h1>');
});
test('handles 404', function() {
$response = request('/nonexistent/');
expect($response->status)->toBe(404);
});
```
## Deployment Architecture
### Simple Deployment
```bash
# 1. Clone repository
git clone https://github.com/you/your-site
# 2. Point web server to directory
# Document root: /path/to/site
# Rewrite all requests to: /app/router.php
# 3. Done
```
### With Build Step (Optional)
```bash
# 1. Clone and build
git clone ...
cd site
# 2. Process custom styles (optional)
# E.g., PostCSS, autoprefixer
# 3. Deploy
rsync -av . server:/var/www/site/
```
### Zero-Downtime Deployment
```bash
# 1. Deploy to new directory
rsync -av . server:/var/www/site-new/
# 2. Symlink switch
ln -sfn /var/www/site-new /var/www/site-current
# 3. Web server serves from /var/www/site-current
```
## Related
- [Philosophy](philosophy.md)
- [Getting Started Tutorial](../tutorial/00-getting-started.md)
- [File Structure Reference](../reference/file-structure.md)
- [Template Reference](../reference/templates.md)

View file

@ -0,0 +1,496 @@
# Philosophy
Understanding the principles and thinking behind FolderWeb.
## Core Idea
**Your file system is your content management system.**
FolderWeb embraces the simplest possible approach to web publishing: create a folder structure that mirrors your site hierarchy, drop files into folders, and they immediately become pages. No database, no admin panel, no build process.
## Design Principles
### 1. Just Enough, Nothing More
FolderWeb applies minimal PHP to enable modern conveniences while remaining maintainable for years or decades. Every feature must justify its existence by solving a real problem without creating new complexity.
**What this means:**
- No frameworks that might be abandoned
- No build tools that need maintenance
- No package managers introducing dependencies
- No abstractions unless they provide lasting value
**Example**: Instead of using a routing library, FolderWeb uses PHP's native file functions to map folders to URLs. This will work identically in 2025 and 2035.
### 2. Longevity Over Novelty
Code should outlive trends. FolderWeb prioritizes stability and backward compatibility over cutting-edge features.
**What this means:**
- Standard PHP (no exotic extensions)
- Plain HTML and CSS (no JavaScript required)
- Simple file formats (Markdown, INI)
- Conventions over configuration
**Why it matters**: A site built today should still work in 10 years without updates. The web's foundational technologies (HTML, CSS, PHP) change slowly and maintain backward compatibility.
### 3. Transparent and Readable
You should be able to open any file and immediately understand what it does. No magic, no hidden behavior.
**What this means:**
- Sparse, meaningful comments
- Descriptive function names
- Simple control flow
- Minimal abstraction layers
**Example**: Want to know how templates work? Open `app/rendering.php` and read 100 lines of straightforward PHP. No framework documentation needed.
### 4. Files Are Content
Your content lives in plain text files you can edit with any text editor. You own your content completely.
**What this means:**
- Content is portable (copy files, migrate easily)
- Version control friendly (Git tracks changes)
- No lock-in (files work without FolderWeb)
- Backup-friendly (copy a folder)
**Example**: Your entire site is a folder structure. Zip it, move it to another server, extract it, and it works. No database export/import, no migration scripts.
## What FolderWeb Is
### A File-Based Router
FolderWeb maps your folder structure to URLs:
```
content/blog/2025-11-02-post/ → yoursite.com/blog/2025-11-02-post/
```
That's it. No route definitions, no controllers, no configuration.
### A Content Renderer
FolderWeb converts Markdown to HTML, wraps it in templates, and serves it. Three steps:
1. Find content files
2. Convert to HTML
3. Wrap in template
### A Minimal Template System
FolderWeb provides just enough templating to avoid repetition:
- Base template for site structure
- Page template for content wrapper
- List templates for directory views
All using plain PHP includes. No template language to learn.
### A Convention Framework
FolderWeb establishes conventions that eliminate configuration:
- `metadata.ini` for structured data
- `cover.jpg` for images
- `YYYY-MM-DD-slug` for dates
- `filename.lang.ext` for translations
Learn the conventions once, apply them everywhere.
## What FolderWeb Is Not
### Not a CMS
No admin panel. Edit files directly with your text editor, commit to Git, deploy.
**Why**: Admin panels add complexity, require maintenance, create security risks, and limit what you can do. Text files are simpler and more powerful.
### Not a Static Site Generator
FolderWeb renders pages on request, not at build time.
**Why**: No build step means immediate feedback. Save a file, refresh your browser, see changes. No waiting for builds, no deployment pipelines required (though you can add them).
### Not a JavaScript Framework
Zero JavaScript in the framework. HTML and CSS only.
**Why**: JavaScript adds complexity, breaks without it, requires builds/transpilation, and changes rapidly. HTML and CSS are stable and sufficient for content sites.
### Not Opinionated About Design
FolderWeb provides minimal default styles. Your design is your own.
**Why**: Design trends change. FolderWeb gives you a clean foundation and gets out of the way. Override everything in `/custom/`.
## Design Decisions Explained
### Why PHP 8.4+?
**Modern features without complexity.**
PHP 8.4 provides:
- Readonly classes (immutability)
- Property hooks (computed properties)
- Arrow functions (concise code)
- Modern array functions
- Asymmetric visibility (controlled access)
These features make code clearer without adding dependencies or build steps. PHP 8.4 will be supported for years.
**Tradeoff**: Requires newer PHP, but gains clarity and performance.
### Why No Database?
**Files are simpler.**
Databases add:
- Setup complexity
- Backup complexity
- Migration complexity
- Performance tuning
- Additional failure points
For content sites, files provide:
- Version control integration
- Simple backups (copy folder)
- Portability
- Transparent storage
- No setup required
**When you might need a database**: User-generated content, real-time updates, complex queries, thousands of pages. For those cases, use a different tool.
### Why INI Files for Metadata?
**Simple, readable, PHP-native.**
INI format:
- No parsing library needed (built into PHP)
- Human-readable and editable
- Supports sections for languages
- Familiar format
**Alternatives considered**:
- **YAML**: Requires library, complex syntax
- **JSON**: Not as human-friendly, no comments
- **TOML**: Requires library, less familiar
- **Frontmatter**: Mixes content and metadata
### Why Markdown?
**Readable as plain text, converts to HTML.**
Markdown is:
- Easy to learn (15 minutes)
- Readable without rendering
- Widely supported
- Future-proof (plain text)
- Version control friendly
**Alternatives supported**: HTML (for complex layouts), PHP (for dynamic content).
### Why No Build Tools?
**Immediate feedback, zero setup.**
Build tools add:
- Installation steps
- Configuration files
- Waiting for builds
- Build failures to debug
- Another thing that can break
Without builds:
- Save file → refresh browser → see result
- No setup (just PHP)
- Nothing to configure
- Nothing to break
**Tradeoff**: Can't use Sass, TypeScript, etc. But you can use modern CSS, which is very capable.
### Why Trailing Slashes?
**Consistency and clarity.**
```
/blog/ # Directory (list view)
/blog # Redirects to /blog/
```
Trailing slashes clarify that URLs represent directories, not files. Consistent URLs prevent duplicate content and simplify routing.
### Why Language Prefixes?
**Clear, hackable URLs.**
```
yoursite.com/en/about/ # English
yoursite.com/no/about/ # Norwegian
```
Language in URL:
- User sees current language
- Can manually change URL
- Bookmarkable per language
- SEO-friendly (clear language signal)
**Default language has no prefix** (shorter, cleaner URLs for primary audience).
## Architectural Patterns
### Immutable Context
The `Context` object is readonly (PHP 8.4):
```php
readonly class Context {
public function __construct(
public private(set) string $contentDir,
public private(set) string $currentLang,
// ...
) {}
}
```
**Why**: Prevents accidental mutation, makes code predictable, enables safe sharing of state.
### Computed Properties
Properties calculate values on-demand:
```php
public string $langPrefix {
get => $this->currentLang !== $this->defaultLang
? "/{$this->currentLang}"
: '';
}
```
**Why**: Keeps related logic together, avoids storing derived data, updates automatically.
### Function-Based API
Core functionality exposed as functions, not classes:
```php
renderFile($ctx, $filePath);
findAllContentFiles($dir, $lang, $defaultLang, $availableLangs);
```
**Why**: Simple to understand, easy to test, no object lifecycle to manage.
### Template Fallback
Check custom, fall back to default:
```php
$custom = "/custom/templates/$name.php";
$default = "/app/default/templates/$name.php";
return file_exists($custom) ? $custom : $default;
```
**Why**: Never modify defaults, always override. Clean separation between framework and customization.
## Performance Philosophy
### Performance Through Simplicity
FolderWeb is fast because it does less:
- No database queries
- No heavy frameworks
- No JavaScript parsing
- Minimal file reads
- Direct PHP includes
**Measured performance**: Page load time displayed in footer. Pride in speed through simplicity.
### Caching Strategy
FolderWeb doesn't implement caching. Instead:
- Use OPcache (PHP bytecode cache)
- Use web server caching (Apache/Nginx)
- Use reverse proxy (Varnish, Cloudflare)
- CSS versioned automatically (MD5 hash)
**Why**: Let specialized tools handle caching. FolderWeb focuses on core functionality.
### Optimization Priorities
1. **Avoid work**: Don't render what's not needed
2. **Use native functions**: PHP's file functions are optimized
3. **Minimal abstraction**: Fewer layers = less overhead
## Security Philosophy
### Defense in Depth
Multiple security layers:
- Path validation (prevent directory traversal)
- Realpath checks (resolve symlinks, verify paths)
- Content root enforcement (files must be in document root)
- Output escaping (prevent XSS)
- MIME type validation (proper content types)
### Simplicity Is Security
Less code = smaller attack surface:
- No database (no SQL injection)
- No user input rendering (no XSS in content)
- No file uploads (no upload vulnerabilities)
- No authentication (no auth bypasses)
**For user-generated content**: Use a different tool. FolderWeb is for static content you control.
## Maintenance Philosophy
### Code Should Age Gracefully
FolderWeb aims to require zero maintenance:
- Standard PHP (no exotic dependencies)
- Minimal third-party code (one library: Parsedown)
- Stable APIs (PHP doesn't break backward compatibility)
- No framework upgrades needed
**Goal**: Deploy once, forget about it. Check in years later, it still works.
### Convention Over Configuration
Fewer configuration options = less to maintain:
- File conventions replace config
- Sensible defaults for everything
- Only configure what's necessary (languages)
### Documentation Is Core
Documentation is part of the project:
- Comprehensive reference
- Clear examples
- Explanation of decisions
- How-to guides for common tasks
**Why**: Future you (or someone else) will thank present you.
## When to Use FolderWeb
### Ideal For
- **Blogs**: Write Markdown, publish immediately
- **Documentation**: Multi-file pages, clear structure
- **Portfolios**: Grid layouts, cover images
- **Marketing sites**: Static content, fast loading
- **Personal sites**: Simple, maintainable
- **Long-term projects**: Will work for decades
### Not Ideal For
- **User-generated content**: No database, no auth
- **E-commerce**: Needs dynamic inventory, checkout
- **Social networks**: Real-time updates, complex data
- **SPAs**: JavaScript-heavy, API-driven
- **Large-scale sites**: Thousands of pages (consider static generation)
### Perfect Fit Scenario
You want a blog or content site that:
- You control all content
- Loads fast
- Requires minimal maintenance
- Will work for years without updates
- Integrates with Git workflow
- Gives you complete control
## Comparison to Alternatives
### vs WordPress
**WordPress**: Full-featured CMS, database-driven, plugin ecosystem, admin panel, requires regular updates
**FolderWeb**: File-based, no database, no plugins, no admin, zero maintenance
**Choose WordPress if**: You need plugins, non-technical editors, or a proven ecosystem
**Choose FolderWeb if**: You want simplicity, longevity, and complete control
### vs Jekyll/Hugo (Static Generators)
**Static Generators**: Build at deploy time, generate HTML files, fast serving, requires builds
**FolderWeb**: Renders on request, no build step, immediate feedback, simpler workflow
**Choose Static Generator if**: You want maximum performance, have build infrastructure
**Choose FolderWeb if**: You want immediate feedback, simpler deployment, dynamic capabilities
### vs Laravel/Symfony (PHP Frameworks)
**Frameworks**: Full-stack, MVC architecture, ORM, routing, complex features
**FolderWeb**: Minimal, file-based routing, no ORM, single purpose
**Choose Framework if**: You're building a complex web application
**Choose FolderWeb if**: You're publishing content and want simplicity
## Future Direction
### Stability Over Features
FolderWeb aims to reach "done" status:
- Core functionality complete
- No major features needed
- Focus on documentation and examples
- Bug fixes and security updates only
### Possible Additions
Only if they maintain simplicity:
- More template examples
- Additional default styles (opt-in)
- Performance optimizations
- Better error messages
### Will Never Add
Features that contradict philosophy:
- JavaScript requirement
- Database integration
- Build process
- Admin panel
- User authentication
- Complex plugin system
## Contributing to FolderWeb
### Align With Philosophy
Proposed changes should:
- Maintain simplicity
- Avoid new dependencies
- Work with PHP 8.4+
- Be maintainable long-term
- Solve real problems
### Ideal Contributions
- Bug fixes
- Performance improvements
- Better documentation
- Example templates
- Test cases
- Clarification of existing code
### Before Adding Features
Ask:
1. Can this be solved in userland (custom templates/code)?
2. Does this add complexity for all users?
3. Will this need maintenance in 5 years?
4. Is this truly necessary?
## Conclusion
FolderWeb is deliberately simple. It does one thing—publishes content from files—and does it well. It resists feature creep, embraces constraints, and prioritizes longevity.
This isn't the right tool for every project. But for content sites that value simplicity, maintainability, and longevity, it might be perfect.
The code you write today should work in 2035. FolderWeb is built on that principle.
## Related
- [Getting Started Tutorial](../tutorial/00-getting-started.md)
- [Architecture Overview](architecture.md)
- [File Structure Reference](../reference/file-structure.md)