Skip to main content
When building applications using Inertia, each page in your application typically has its own controller / route and a corresponding JavaScript component. This allows you to retrieve just the data necessary for that page - no API required. With inertia-server, working with pages is based on the concept of page definitions created by the definePage function.
const { definePage } = createInertia(...);

Creating page definitions

Page definitions consist of the component name, and a set of props that will be passed to it.
~/server/inertia.ts
import { createInertia, prop, mergedProp, deepMergedProp, type PageProps } from 'inertia-server';

const homePage = definePage({
  component: 'Home',
  props: {
    title: prop<string>(),
    description: prop<string>(),
    posts: mergedProp<Posts[]>(),
    tasks: deepMergedProp<Tasks[]>().deferred(),
  },
  // Make sure that the 'user' shared key is available
  // during render, otherwise it is optional
  requireShared: ['user'],
});

export type HomePageProps = PageProps<typeof homePage>;
When handling inertia requests, you can render your page by passing a page context to the render function. It is created by invoking your page definition with the required props.
inertia.render(homePage({
  title: 'Welcome',
  description: 'Hello, World!',
  posts: () => getPosts(req.params.page),
  tasks: () => getTasks(),
}));
On the client, you can use the created definitions to access your data with full type safety.
import type { HomePageProps } from '~/server/inertia';

function Home({ user, posts, tasks }: HomePageProps) {
  /**
   * interface Props {
   *  title: string;
   *  description: string;
   *  posts: Posts[];
   *  tasks?: Tasks[];
   *  user: User
   * }
  */
  const { title, description, posts, tasks } = props;
  return (
    <div>
      <h1>{title}</h1>
      <p>Hello, {user.name}! {description}</p>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
      {tasks ? (
        <ul>
          {tasks.map((task) => (
            <li key={task.id}>{task.title}</li>
          ))}
        </ul>
      ) : "Loading tasks..."}
    </div>
  );
}

Data & Props

Page props builders - prop, mergedProp, deepMergedProp provide various chained methods to help you customize your data loading behavior.

Merging props

Inertia overwrites props with the same name when reloading a page. However, you may need to merge new data with existing data instead. For example, when implementing a “load more” button for paginated results. Prop merging only works during partial reloads. Full page reloads will override the props with the new values.
const page = definePage({
  component: 'Home',
  props: {
    posts: mergedProp<Posts[]>(),
  },
});
By default, new data will be appended to the end of the existing data. You can change this behavior by chaining the prepend method.
const page = definePage({
  component: 'Home',
  props: {
    posts: mergedProp<{ id: number }[]>().prepend(),
  },
});
If you want to not only append, but replace some of the existing data, provide a matchOn option. By sending items with the same key, elements will be replaced, and rest - appended or prepended.
const page = definePage({
  component: 'Home',
  props: {
    posts: mergedProp<{ id: number, value: string }[]>({ matchOn: "id" }),
  },
});

Deep merging

Instead of specifying which nested paths should be merged, you may use deepMergedProp to ensure a deep merge of the entire structure.
const page = definePage({
  component: 'Chat',
  props: {
    chat: deepMergedProp<{ 
      messages: { id: number; text: string }[], 
      online: 12
    }>({ matchOn: "meesges.id" }),
  },
});
Deep merging was introduced before normal merging had support for prepending and targeting nested paths. In most cases, mergedProp() with matchOn and its append and prepend methods should be sufficient.

Infinite scrolling

Merged props can be used to implement infinite scrolling with inertia <InfiniteScroll> component.
const page = definePage({
  component: 'Home',
  props: {
    posts: mergedProp<Posts[]>().scroll(),
  },
});
In this case, inertia will try to read page query parameter from your request and use it as the page number. To customise it, you can use the pageName option.
const page = definePage({
  component: 'Home',
  props: {
    posts: mergedProp<Posts[]>().scroll({ pageName: 'page' }),
  },
});
When rendering a page, inertia will require you to provide the hasMore prop. It will be used to determine if there is more data to load.
inertia.render(page({
  posts: () => getPosts(page),
  otherScrollProp: () => getOtherScrollProp(page),
  $hasMore: {
    posts: true,
    otherScrollProp: true,
  },
}));
To make backend integration easier, you can read the page parameter name from the page definition.
function handler(req: Request) {
  const pageQueryParam = postsPage.scrollOptions.posts.pageName;
  const page = req.query[pageQueryParam];
}

Lazy loading

When rendering a page, every prop value can be a literal, or a lazy evaluated function. It’s up to you which data loading strategy to use. Let’s see how it works on an example.
const page = definePage({
  component: 'Home',
  props: {
    foo: prop<string>(),
    bar: prop<string>().deferred(),
  },
})

// Option 1
inertia.render(page({
  foo: 'foo',
  bar: () => getBar(),
}));

// Option 2
inertia.render(page({
  foo: () => 'foo',
  bar: getBar(),
}));
In both cases, the foo prop will be sent during every page request, but if you define its value as a function, it will be resolved when inertia needs to parse its value, not at the moment of the render call. It is especially useful when you work with partial data requests. Interestingly, the bar prop will not be parsed by inertia on the first request. By default you should provide a lazy evaluable function to defer execution of your code until it’s needed. But if you want to, you can provide a literal value at the moment of the render call. It will not be sent to the client, but it may useful if you want to execute some side effect when a page is rendered and the prop prepared, no matter whether its loaded or not.

Deferred props

To defer a prop, you can use the deferred() method on the prop definition.
const page = definePage({
  component: 'Home',
  props: {
    foo: prop<string>().deferred(),
  },
});
By default, all deferred props get fetched in one request after the initial page is rendered, but you can choose to fetch data in parallel by grouping props together.
const page = definePage({
  component: 'Home',
  props: {
    permissions: prop<string[]>().deferred(),
    teams: prop<string[]>().deferred('attributes'),
    roles: prop<string[]>().deferred('attributes'),
  },
});
In the example above, the teams and roles props will be fetched in one request, while the permissions prop will be fetched in a separate request in parallel. Group names are arbitrary strings and can be anything you choose. On the client side, inertia provides the Deferred component to handle deferred props. It is not required with inertia-server, because we correctly type deferred props as optional, so you can just check for the value and show a loading state. If you need to differentiate between the loading state and a potential empty value, use the component though.

Prop modifiers

When Inertia performs a request, it will determine which data is required and only then will it evaluate the closure. With prop modifiers, you can manually control this behavior. Use optional() method to specify that a prop should never be included unless explicitly requested using the only client option. On the reverse, you can use always() method to specify that a prop should always be included, even in partial reloads. To load a prop on the first request, and then to keep it in the cache, you can use once() method.
const page = definePage({
  component: 'Home',
  props: {
    foo: prop<string>().optional(),
    bar: prop<string>().always(),
    baz: prop<string>().once(),
  },
});

Accessing other page object properties

Flash data and errors are available inside the props object. To access page url, version and other metadata, use hooks from your framework of choice.
react
import { usePage } from '@inertiajs/react';
const { url, version } = usePage();

return (
  <div>
    <h1>{url}</h1>
    <p>{version}</p>
  </div>
);