vwf .

Core Framework APIs

Core utilities for routing, state management, validation, and component composition

🧭 File-Based Router V2

Zero-config routing with automatic route discovery, nested layouts, and lifecycle hooks - similar to Next.js App Router.

Core Concepts

  • • Routes discovered from file structure automatically
  • • page.js files define routes (not default exports)
  • • layout.js files define nested layouts
  • • Directory names become URL segments
  • • No manual route configuration needed

Navigation API

import { router } from './utils/fileBasedRouterV2.js';

// Basic navigation
router.navigate('/dashboard');

// Navigate with state
router.navigate('/profile', { userId: 123 });

// Navigate with direction hint for transitions
router.navigate('/auth/email', null, 'forward');

// Go back
router.back();

// Get current path
const path = router.currentPath();

// Check if path matches
if (router.matches('/dashboard')) {
  // Currently on dashboard
}

File Structure Convention

src/app/
├── layout.js              # Root layout (wraps all pages)
├── page.js                # Home page (/)
├── dashboard/
│   └── page.js            # Dashboard (/dashboard)
├── profile/
│   ├── page.js            # Profile (/profile)
│   └── edit/
│       └── page.js        # Profile edit (/profile/edit)
└── auth/
    ├── layout.js          # Auth layout
    ├── page.js            # Auth landing (/auth)
    └── email/
        └── page.js        # Email entry (/auth/email)

Page Component Pattern

// src/app/dashboard/page.js
import van from 'vanjs-core';
const { div, h1 } = van.tags;

// IMPORTANT: export const component (not default!)
export const component = () => {
  return div({ class: 'dashboard-page' },
    h1('Dashboard'),
    // Page content
  );
};

// Optional: Lifecycle hooks
component.postMount = () => {
  console.log('Dashboard mounted');
  // Initialize maps, charts, fetch data
};

component.preUnmount = () => {
  console.log('Dashboard unmounting');
  // Cleanup timers, listeners, resources
};

// Optional: Route metadata (future)
export const metadata = {
  title: 'Dashboard',
  requiresAuth: true
};

Layout Component Pattern

// src/app/auth/layout.js
import van from 'vanjs-core';
import { AuthCard } from '../../shared-components/layout/AuthCard.js';
const { div } = van.tags;

// Layout receives outlet prop
export const component = ({ outlet }) => {
  return div(
    { class: 'auth-layout min-h-screen' },
    AuthCard({},
      outlet  // Child pages render here
    )
  );
};

Benefits

  • • Zero configuration - routes auto-discovered
  • • Better organization - related files together
  • • Nested layouts - reduce code duplication
  • • Smart diffing - minimal DOM updates
  • • Familiar pattern - like Next.js App Router

Features

  • • Automatic route discovery
  • • Nested layouts support
  • • Lifecycle hooks (postMount, preUnmount)
  • • Fixed outlet system for smooth transitions
  • • State preservation in layouts

✓ Form Validation (validator.js)

Comprehensive validation library for forms with 80+ validators for email, phone, URL, password strength, and more.

Common Validators

ValidatorDescriptionExample
isEmail()Validate email addressuser@example.com
isMobilePhone()Validate phone number+1-555-123-4567
isURL()Validate web addresshttps://example.com
isLength()Check string lengthmin: 8, max: 20
isStrongPassword()Password strengthminLength, minSymbols
isAlphanumeric()Letters and numbers onlyusername123
isNumeric()Numbers only12345
matches()Regex pattern match/^[A-Z]/

Basic Usage

import validator from 'validator';

// Email validation
const isValid = validator.isEmail('user@example.com');
// → true

// Phone validation with locale
validator.isMobilePhone('+1-555-123-4567', 'en-US');
// → true

// URL validation
validator.isURL('https://example.com');
// → true

// Length validation
validator.isLength('password123', { min: 8, max: 20 });
// → true

// Password strength
validator.isStrongPassword('Abc123!@#', {
  minLength: 8,
  minLowercase: 1,
  minUppercase: 1,
  minNumbers: 1,
  minSymbols: 1
});
// → true

Real-Time Validation Pattern

import validator from 'validator';

const email = van.state('');
const emailMsg = van.state({});

const validateEmail = (value) => {
  if (!value) {
    return { code: 'error', detail: 'Email is required' };
  }
  if (!validator.isEmail(value)) {
    return { code: 'error', detail: 'Invalid email address' };
  }
  return { code: 'success', detail: 'Email valid!' };
};

// In your component
Input({
  label: 'Email',
  type: 'email',
  value: email,
  message: emailMsg,
  placeholder: 'you@example.com',
  oninput: (e) => {
    email.val = e.target.value;
    if (e.target.value) {
      emailMsg.val = validateEmail(e.target.value);
    }
  }
})

Multi-Field Validation

const validatePassword = (value) => {
  if (!value) {
    return { code: 'error', detail: 'Password is required' };
  }
  if (!validator.isLength(value, { min: 8 })) {
    return { code: 'warning', detail: 'Minimum 8 characters' };
  }
  if (!/[A-Z]/.test(value)) {
    return { code: 'warning', detail: 'Add uppercase letter' };
  }
  if (!/[0-9]/.test(value)) {
    return { code: 'warning', detail: 'Add number' };
  }
  return { code: 'success', detail: 'Strong password!' };
};

const handleSubmit = async (e) => {
  e.preventDefault();

  // Validate all fields
  emailMsg.val = validateEmail(email.val);
  passwordMsg.val = validatePassword(password.val);

  // Check if all valid
  if (emailMsg.val.code !== 'success' ||
      passwordMsg.val.code !== 'success') {
    return;
  }

  // Submit form
  await submitForm({ email: email.val, password: password.val });
};

More Validators Available

isJSON()isDate()isCreditCard()isIP()isPostalCode()isHexColor()isBase64()isJWT()isBefore()isAfter()isEmpty()contains()

80+ validators available - see validator.js documentation

✨ Best Practices

Router Best Practices

  • • Keep pages focused - logic in components
  • • Use layouts for shared UI (headers, footers)
  • • Implement lifecycle hooks for cleanup
  • • Follow naming: page.js and layout.js
  • • Organize by feature in directories

Validation Best Practices

  • • Validate on input for real-time feedback
  • • Validate on submit for final check
  • • Provide clear error messages
  • • Use warning for suggestions, error for blocks
  • • Show success states for confidence

Performance Tips

  • • Debounce expensive validations
  • • Cache validation results when possible
  • • Clean up resources in preUnmount
  • • Use lifecycle hooks appropriately
  • • Avoid validation in tight render loops

Common Patterns

  • • Create reusable validator functions
  • • Centralize validation logic
  • • Use message states for feedback
  • • Implement progressive validation
  • • Handle async validation gracefully

🔗 Related Resources

Explore vwf

Discover what vwf can do