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 is a LaraGram port of the ideas behind Luna.js. 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
Install the PHP adapter with Composer:
composer require laraxgram/lunaPublish 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.