Introduction

The problem

I had a PHP back end and a Vue front end, and I wanted server-side rendering. That meant running Node.js next to PHP on the server, which felt wrong. It also meant writing everything twice: the same models and validation in PHP for the server and in JavaScript for the browser, and keeping them in sync.

The idea

Write the component in PHP once, and use it in two places:

  • On the server it renders the full HTML (SSR). The first load is fast, search engines see the content, and the page shows up before any JavaScript loads.
  • In the browser the same component, transpiled to JavaScript, takes over the page (hydration). After that it is a normal client-side app: reactivity, routing between pages, form state. None of that goes back to the server, so there is no round trip per click. The server gets involved only when you need data, through a regular API call.

A component is a PHP class with an HTML template:

class CounterExample extends BaseComponent
{
    public int $count = 0;

    public function increment()
    {
        $this->count++;
    }
}
<button (click)="increment">Clicked $count times.</button>

The result:

And this is the JavaScript Viewi generates from that class:

class CounterExample extends BaseComponent {
    _name = "CounterExample";
    count = 0;
    increment() {
        var $this = this;
        $this.count++;
    }
}

The class keeps its structure and names, so the code running in the browser is easy to read and debug. PHP functions such as count, array_filter or array_map work too, because Viewi ships JavaScript versions of them, so you keep writing PHP the way you are used to.

Viewi is framework agnostic: it works with any PHP framework, or without one. The docs have integration guides for Laravel, CodeIgniter 4, Leaf and ReactPHP. It is not tied to the classic PHP runtime either, and runs on Swoole too.

Not limited to PHP

Viewi generates a regular JavaScript project for your app, and you can extend it with any JavaScript or TypeScript code and any npm package. When you need a browser SDK or library, like payments, a rich text editor or charts, you write a small TypeScript module and call it from your component. You can also replace a component's JavaScript completely. See Extending with JavaScript.

How it works

Build. Viewi reads your components and transpiles them to JavaScript. Node.js bundles the result with esbuild. This is the only place Node.js is used.

First request. PHP renders the page and sends plain HTML. If a component calls your API while rendering, the call goes straight to your application inside the same PHP process, and the response is sent with the page.

In the browser. The JavaScript attaches to the HTML the server already rendered and reuses the data sent with the page instead of requesting it again. From there the page is reactive, and moving to another page happens in the browser, calling your API over HTTP.

Compared to Livewire

With Livewire, the component lives on the server. A click or input goes to the server, PHP renders again, and the HTML changes come back. With Viewi, the component runs in the browser after the first render, and the server only answers API calls.

If you are on Laravel and want server-driven pages with some interactivity, Livewire is a good fit. If you want a single-page app written in PHP, without a round trip on every interaction, or you are not on Laravel, Viewi fits.

If you do not need server-side rendering and you are happy with Vue, React or Svelte on the front end, Viewi is probably not worth switching to. It is for PHP developers who want both sides in one language.

Trade-offs

  • Components are written in a subset of PHP, because they have to transpile to JavaScript. You can not use a Composer package in a component. Business logic stays in your back end, behind an API.
  • There is a build step, and the browser downloads a JavaScript bundle. Livewire ships less JavaScript.
  • The community is much smaller than Livewire's, so there are fewer ready-made components and answers online.
  • You need to think in terms of "client app + API" for data. Livewire hides that boundary from you.

What you should know

The browser runs JavaScript, not PHP, so a few things work differently:

  • Component code can only use Viewi and the classes in your Viewi folder: components, services, models. Using a class from your framework or application fails the build. Get data from your API with the HTTP client.
  • Viewi has JavaScript versions of 324 PHP functions. See PHP functions in the browser.
  • Class names must be unique across your Viewi application, because the generated JavaScript has no namespaces.

The Caveats page covers the rest.

See a real project: PHP components and the JavaScript generated from them.