PyxlePyxle/Docs

.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 @server loader -- an async function that fetches data for the component
  • Define @action mutations -- 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 @server loader per file. The loader receives a Starlette Request and must return a JSON-serializable dict.
  • Multiple @action functions are allowed. Each becomes a callable endpoint.
  • The @server function must be async. Pyxle enforces this at compile time.
  • Imports are auto-detected. Lines starting with import or from are classified as Python.
  • The @server and @action decorators 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. The data prop contains whatever the @server loader returned. If there is no loader, data is an empty object {}.
  • Can import from pyxle/client. This gives you <Head>, <Script>, <Image>, <ClientOnly>, <Form>, useAction, <Link>, navigate, and prefetch.
  • Can import from node_modules. Any npm package in your package.json is 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 HEAD Python 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