.pyxl Files
A .pyxl file is the fundamental building block of a Pyxle application. It combines Python server logic with a React component in a single file -- your data fetching and your UI live together.
Anatomy of a .pyxl file
A .pyxl file has two sections:
# 1. Python section -- runs on the server
from datetime import datetime
@server
async def load_page(request):
return {"now": datetime.now().isoformat()}// 2. JSX section -- runs on both server (SSR) and client
import { Head } from 'pyxle/client';
export default function MyPage({ data }) {
return (
<>
<Head>
<title>My Page</title>
</Head>
<h1>Current time: {data.now}</h1>
</>
);
}The compiler automatically detects which lines are Python and which are JSX. Python code uses @server/@action decorators, imports, and standard Python syntax. Everything else is treated as JSX.
The Python section
The Python section runs entirely on the server. It can:
- Import modules -- any Python package available in your environment
- Define a
@serverloader -- an async function that fetches data for the component - Define
@actionmutations -- async functions callable from the client
from pyxle.runtime import server, action
import httpx
@server
async def load_users(request):
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/users")
return {"users": resp.json()}
@action
async def delete_user(request):
body = await request.json()
user_id = body["id"]
# ... delete from database ...
return {"deleted": user_id}Rules for the Python section
- One
@serverloader per file. The loader receives a StarletteRequestand must return a JSON-serializable dict. - Multiple
@actionfunctions are allowed. Each becomes a callable endpoint. - The
@serverfunction must beasync. Pyxle enforces this at compile time. - Imports are auto-detected. Lines starting with
importorfromare classified as Python. - The
@serverand@actiondecorators are available globally -- you do not need to import them (the compiler injects the import automatically).
The JSX section
The JSX section is a standard React component. It runs on both the server (for SSR) and the client (for hydration and interactivity).
import { Head } from 'pyxle/client';
export default function MyPage({ data }) {
const [count, setCount] = React.useState(0);
return (
<>
<Head>
<title>Users</title>
<meta name="description" content="Our user directory." />
</Head>
<div>
<h1>Users: {data.users.length}</h1>
<button onClick={() => setCount(c => c + 1)}>
Clicked {count} times
</button>
</div>
</>
);
}Rules for the JSX section
- Must have a default export. The default export is the page component.
- Receives
{ data }as props. Thedataprop contains whatever the@serverloader returned. If there is no loader,datais an empty object{}. - Can import from
pyxle/client. This gives you<Head>,<Script>,<Image>,<ClientOnly>,<Form>,useAction,<Link>,navigate, andprefetch. - Can import from
node_modules. Any npm package in yourpackage.jsonis available. - Cannot import Python code. The Python and JSX sections are compiled separately.
Controlling the document <head>
Use the <Head> component from pyxle/client to control what goes in the document <head>:
import { Head } from 'pyxle/client';
export default function AboutPage({ data }) {
return (
<>
<Head>
<title>About Us</title>
<meta name="description" content="Our story" />
<link rel="canonical" href="https://example.com/about" />
</Head>
<h1>About Us</h1>
</>
);
}Anything inside <Head> is extracted during SSR and inlined into the document <head>. It supports dynamic values via normal JSX interpolation:
import { Head } from 'pyxle/client';
@server
async def load_post(request):
post = await fetch_post(request.path_params["slug"])
return {"post": post}
export default function BlogPost({ data }) {
return (
<>
<Head>
<title>{data.post.title} — My Blog</title>
<meta name="description" content={data.post.excerpt} />
</Head>
<article>
<h1>{data.post.title}</h1>
{/* ... */}
</article>
</>
);
}Head values are automatically sanitised to prevent XSS injection -- angle brackets inside <title> text are escaped, event handler attributes are stripped, and javascript: URLs are neutralised.
Note: Pyxle also supports a lower-level
HEADPython variable for the rare cases where you want fully static head metadata extracted at compile time. For everyday pages, prefer the<Head>component. See Head Management for both mechanisms and when to use each.
A complete example
from datetime import datetime, timezone
@server
async def load_home(request):
hour = datetime.now(tz=timezone.utc).hour
if hour < 12:
greeting = "Good morning"
elif hour < 18:
greeting = "Good afternoon"
else:
greeting = "Good evening"
return {"greeting": greeting}import { Head } from 'pyxle/client';
export default function HomePage({ data }) {
return (
<main>
<Head>
<title>{data.greeting}</title>
</Head>
<h1>{data.greeting}</h1>
<p>Welcome to Pyxle.</p>
</main>
);
}JSX-only files
If a page has no server logic, you can write a JSX-only .pyxl file:
export default function AboutPage() {
return (
<main>
<h1>About</h1>
<p>This page has no loader -- it renders the same content every time.</p>
</main>
);
}Next steps
- Learn how files map to URLs: Routing
- Add data fetching: Data Loading