Luna
Introduction
Luna is the official adapter that lets you build modern, single-page style frontends for your LaraGram application using React, Vue, or Svelte — without building an API, without client-side routing configuration, and without losing any of the server-side conveniences you already know: routing, controllers, middleware, validation, and authorization.
Luna has two halves that ship together: the LaraGram adapter you see in this page, and the Luna-JS client runtime that drives the browser. You write your routes and controllers exactly as you always have. Instead of returning a Blade view or JSON, a controller returns a Luna response, naming a JavaScript page component and the props it should receive:
use LaraGram\Luna\Luna;
class UserController extends Controller
{
public function show(User $user)
{
return Luna::render('Users/Show', [
'user' => $user,
]);
}
}On the first visit, LaraGram returns a full HTML document with your page component embedded. On every visit after that, Luna intercepts the navigation, fetches only the new page's props over XHR, and swaps the component client-side — giving you the feel of a SPA while keeping all of your application logic on the server.
NOTE
Luna ships in two halves: the laraxgram/luna PHP package (the server-side adapter for LaraGram) and the @laraxgram/luna family of npm packages (the client-side runtime plus the @laraxgram/react, @laraxgram/vue3, and @laraxgram/svelte adapters). You install both.
When to Use Luna
Luna serves two related purposes, and this documentation covers both:
Building SPAs for LaraGram. Use Luna as a drop-in replacement for a hand-rolled API + client-side router. Your team writes controllers and page components; Luna wires them together. This is the "classic" Luna experience, and everything in Pages & Props, Routing & Visits, Forms, and Frontend Setup applies.
Building Telegram Mini Apps (TMA). Luna's primary reason for existing. A Telegram Mini App is a web app that runs inside the Telegram client. Luna adds cryptographic init-data validation, a
telegramauth guard, a reactive theme/viewport store, native-button ↔ form integration, and promise-based wrappers for the entire Telegram WebApp SDK. See Telegram Mini Apps and Telegram Features.
The two use cases share one runtime. A Mini App is a Luna SPA that additionally speaks Telegram; read the SPA chapters first, then layer the Telegram chapters on top.
How Luna Works
Luna sits between LaraGram's router and your JavaScript framework. The lifecycle of a request looks like this:
First visit (full page load). The browser requests a URL normally. Your route resolves to a controller that returns
Luna::render('Component', $props). Luna renders your root Blade template (app.blade.phpby default), embedding the page object — component name, props, URL, and asset version — as JSON in adata-pageattribute on the root element. The browser boots your JS framework, which reads that object and mounts the named component.Subsequent visits (Luna visits). When the user clicks a Luna
<Link>or you call the router, Luna issues an XHR request carrying theX-Luna: trueheader. LaraGram sees the header and — instead of returning HTML — returns the page object as JSON. Luna swaps the page component and props client-side, updates the browser history, and the user never sees a full reload.
Because the server decides which component renders, you keep a single source of truth for routing, authorization, and validation. There is no second router to keep in sync.
The Luna Protocol
Every Luna response — whether HTML or JSON — carries the same page object:
| Key | Description |
|---|---|
component | The name of the JS page component to render, e.g. Users/Show. |
props | The data passed to the component. |
url | The URL of the current page. |
version | The current asset version, used to force a full reload when assets change. |
You will rarely interact with the protocol directly — the PHP adapter and the JS runtime handle it — but understanding it makes the rest of the documentation click into place.
Server-Side Installation
Luna is an add-on for the LaraGram framework: it renders pages from your LaraGram controllers, authenticates Mini App users through LaraGram's auth guards, and is configured like any other LaraGram component. It always lives inside a LaraGram application.
Starting From a Starter Kit
The quickest way — and the recommended one — is to create the application with the LaraGram installer and pick one of the starter kits. Each kit ships with Luna already wired up: the PHP adapter, the npm packages, the root template, Vite, and an example page:
composer global require laraxgram/installer
laragram new my-app
cd my-app
npm install && npm run build
composer run devThe installer asks which starter kit you want; React, Vue and Svelte are all Luna based, so everything on this page is already in place and you may skip straight to Your First Page.
Adding Luna to an Existing Application
To add Luna to an application you already have, require the PHP adapter with Composer:
composer require laraxgram/lunaLuna needs PHP 8.2 or newer with the json and sodium extensions, and a LaraGram 4 application with the web layer enabled.
Publish the configuration file. This creates config/luna.php, where you configure server-side rendering, page paths, history encryption, and Telegram options:
php laragram vendor:publish --provider="LaraGram\Luna\ServiceProvider"The Root Blade Template
Luna needs a single root Blade template that loads your compiled assets and provides the mounting point. By convention it lives at resources/views/app.blade.php. Use the @luna directive for the root element and @lunaHead for server-rendered <head> content:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@lunaHead
@vite(['resources/js/app.js'])
</head>
<body>
@luna
</body>
</html>@lunarenders the root<div>and embeds the initial page object.@lunaHeadrenders any<title>,<meta>, and other head elements produced by server-side rendering.@viteloads your compiled JS/CSS via the Vite plugin.
If you name your root template something other than app, tell Luna about it globally in a service provider:
use LaraGram\Luna\Luna;
Luna::setRootView('layout');Or per-response:
return Luna::render('Users/Show', ['user' => $user])
->rootView('layout');Client-Side Installation
Install the core runtime plus the adapter for your framework of choice:
# React
npm install @laraxgram/luna @laraxgram/react
# Vue 3
npm install @laraxgram/luna @laraxgram/vue3
# Svelte
npm install @laraxgram/luna @laraxgram/svelteInstall the Vite plugin as a dev dependency — it wires up page auto-resolution and the SSR dev server:
npm install -D @laraxgram/viteInitializing the App
Create your app entry point (e.g. resources/js/app.js) and boot Luna with createLunaApp. The resolve callback maps a component name to a component module; the setup callback mounts the resolved app. Here is the React version:
import { createLunaApp } from '@laraxgram/react'
import { createRoot } from 'react-dom/client'
createLunaApp({
resolve: (name) => {
const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
return pages[`./Pages/${name}.jsx`]
},
setup({ el, App, props }) {
createRoot(el).render(<App {...props} />)
},
})The equivalent Vue and Svelte entry points — plus the Vite plugin's pages shorthand that removes the import.meta.glob boilerplate — are covered in Frontend Setup. Configure the Vite plugin in vite.config.js:
import luna from '@laraxgram/vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
react(),
luna(),
],
})Your First Page
Page components are ordinary framework components stored under resources/js/Pages. They receive their props from the controller. A React page:
// resources/js/Pages/Users/Show.jsx
import { Head, Link } from '@laraxgram/react'
export default function Show({ user }) {
return (
<>
<Head title={user.name} />
<h1>{user.name}</h1>
<p>{user.email}</p>
<Link href="/users">Back to users</Link>
</>
)
}Wire a route to it. Any controller returning Luna::render() works, but for prop-less pages Luna adds a Route::luna() shortcut that renders a component directly:
use LaraGram\Support\Facades\Route;
// Full control via a controller...
Route::get('/users/{user}', [UserController::class, 'show']);
// ...or the shorthand for a component with static props.
Route::luna('/about', 'About');That's a complete round trip: a route, a controller returning a named component with props, and a page component that renders them. From here, add links and visits, forms, and richer props.
Where to Go Next
- Pages & Props — rendering components, shared props, deferred/optional/merged props, and partial reloads.
- Routing & Visits — the
<Link>component, manual router visits, redirects, prefetching, polling, and history. - Forms — the
useFormhelper, the<Form>component, validation errors, file uploads, and Precognition. - Frontend Setup — per-framework setup, persistent layouts,
<Head>, title/meta, code splitting, the Vite plugin, and server-side rendering. - Telegram Mini Apps — building TMAs: init-data validation, the
telegramguard, the reactive store, and native-button integration. - Telegram Features — CloudStorage, biometrics, location, sensors, haptics, popups, sharing, invoices, and the test mock.