Skip to content
 
 

Latest commit

 

History

35 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

php-router

A fast, file-based AJAX router for PHP — powered by page.js on the client and a slim PHP resolver on the server.

Built and maintained by Euan Livingstone. Originally forked from Coasters & Crafters Ltd (now Bundle Group Ltd).

Why this exists

You get Next.js-style file routing with plain PHP pages, without a heavy framework. Navigate client-side, render server-side fragments, and keep URLs, params, and query strings first-class.

Changes from the original

Forked from Coasters & Crafters Ltd (now Bundle Group Ltd). Notable differences:

Area Original This fork
Query strings Not forwarded to PHP ?key=value available as $_QUERY / $_GET
Client cache Every navigation hit the network In-memory GET cache for repeat / back-forward visits
Prefetch None Hover prefetch on same-origin links
Race handling Overlapping fetches could clash In-flight requests aborted on new navigation
Hooks None Router.on('beforeNavigate' | 'afterNavigate')
Public API Fetch-only Router.prefetch(), Router.clearCache()
Loading UX None router-loading class on the content root
Page scripts <script> in fragments did not run Inline + external scripts re-executed on each navigation
Active nav Unused navBarWrapperSelector Auto active + aria-current on matching nav links
PHP resolver Simple path parse, no guards Cached path resolve, path-traversal block, strict_types
page.js CDN rawgit (deprecated) unpkg
License / credit © Coasters & Crafters Ltd © Euan Livingstone, with original fork attribution

File-based routing (pages/files, [param] segments, customRoutes in router.js) is unchanged in spirit — same mental model, faster and safer plumbing.

Features

  • File-based routes under pages/files (including [param] segments)
  • Query strings?this=that is available in PHP as $_QUERY and $_GET
  • Route params/users/:id$_PARAMS['id']
  • In-memory page cache — back/forward and repeat visits feel instant
  • Prefetch on hover — links warm the cache before you click
  • Abort in-flight fetches — rapid clicks don’t race
  • Navigation hooksRouter.on('beforeNavigate' | 'afterNavigate', fn)
  • Page scripts — inline and external <script> tags in page fragments run on every visit
  • Active nav markers — underline / style the current page link in navbars
  • Path traversal protection and stricter request handling on the PHP side

Installation

git clone https://github.com/einc123/php-router.git

Point your web server so unknown paths fall through to index.php (see page.js server configuration).

Quick start

php -S localhost:8080

Open http://localhost:8080.

Routing

Static pages

https://example.com/help → create pages/files/help.php.

Params

https://example.com/users/1 → create pages/files/users[id].php, then register the route in assets/js/router.js:

const customRoutes = [
    '/users/:id',
];

In PHP:

<?php echo htmlspecialchars($_PARAMS['id'], ENT_QUOTES, 'UTF-8'); ?>

Multiple params: settings[name][value].php with route /settings/:name/:value.

Nested routes: use a folder like users[id]/ with index.php (and optional child files such as email.php).

Query strings

https://example.com/news/42?tab=comments

$_PARAMS['id'];   // "42"
$_QUERY['tab'];   // "comments"
$_GET['tab'];     // also "comments"

POST / other methods

const resp = await fetch('/pages/router.php?path=/your/path/in/router.js', {
    method: 'POST',
    body: JSON.stringify({ your: 'body' }),
    headers: { 'Content-Type': 'application/json; charset=UTF-8' },
});

After mutating data, clear cached HTML if needed:

Router.clearCache();        // all
Router.clearCache('/users/1'); // one key

Hooks & prefetch

Router.on('beforeNavigate', (ctx) => {
    console.log('leaving for', ctx.path);
});

Router.on('afterNavigate', (ctx) => {
    document.title = 'App — ' + ctx.path;
});

Router.prefetch('/news/1?tab=latest');

Active navbar links

Mark a nav with data-router-nav (or use .navbar-nav). On each navigation the best-matching link gets class active and aria-current="page".

<nav data-router-nav>
    <a href="/">Home</a>
    <a href="/news/1">News</a>
    <a href="/users/1">Users</a>
</nav>
[data-router-nav] a.active {
    text-decoration: underline;
    /* or border-bottom, color, etc. */
}
  • / matches exact by default; other links match as a prefix (/users/users/1/email).
  • Override per link with data-active-match="exact" or data-active-match="prefix".
  • Use data-active-path="/news" when the link href is a concrete URL but the section should stay active for all /news/... pages.
  • Longest match wins when several links could be active.
  • Optional: data-active-parent="li" also adds active to a parent element.
  • Call Router.setActive() after you inject a navbar dynamically.

Tune selectors / class name at the top of assets/js/router.js (navSelector, activeClass).

Scripts on pages

Browsers ignore <script> tags inserted via innerHTML. This router re-runs them after each navigation (inline and src), in order.

<button type="button" id="go">Click</button>
<script src="/assets/js/pages/news.js"></script>
<script>
document.getElementById('go').addEventListener('click', () => alert('works'));
</script>
  • Use data-router-once on an external script to load that URL only once (shared libraries).
  • Listen for router:leave / router:enter on <main> to clean up listeners or timers when leaving a page.
document.querySelector('main').addEventListener('router:leave', () => {
    // remove listeners, clear intervals, etc.
});

Custom 404

Edit pages/404.html.

Shared PHP bootstrap

In pages/router.php, uncomment / set:

require_once __DIR__ . '/bootstrap.php';

License

MIT © Euan Livingstone. See license.

Originally forked from Coasters & Crafters Ltd (now Bundle Group Ltd).

About

Fast & Easy PHP Router

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages