Creating a full-stack application using Inertia.js

xandervdb1

Xandervdb

Posted on June 18, 2023

Creating a full-stack application using Inertia.js
✏️ Written by Xander Van den Bossche

For the past month I have been working on a full-stack application together with co-learners at a bootcamp, all the way from coming up with the idea for a product to deploying it. When it came to deciding which technologies we would use to bring this project to a good end, the front-end team was set on using on React for quick rendering of a single-page application, while back-end wanted to work with Laravel so they could benefit from an easy-to-use relational database. None of us had ever worked in a stack that combined the both, but after some asking around we found our solution: Inertia.js.

🤔 What is Inertia.js?

Inertia allows to combine both server-side rendering together with client-side rendering, with the use of routes and controllers which will render a single-page application using React pages and components. It isn't a framework like you might think, but rather acts like the glue between your favorite server-side and client-side frameworks, making them best friends.

The office - Michael hugging Jim gif

As found on the Inertia website:

Inertia is a new approach to building classic server-driven web apps. We call it the modern monolith.

Inertia allows you to create fully client-side rendered, single-page apps, without the complexity that comes with modern SPAs. It does this by leveraging existing server-side patterns that you already love.

Inertia has no client-side routing, nor does it require an API. Simply build controllers and page views like you've always done! Inertia works great with any backend framework, but it's fine-tuned for Laravel.

When we first read about this we thought we were in for a ride to learn about all the ins and outs of Inertia before we'd be able to use all of its benefits. I was in charge of researching the main differences from the regular frameworks, but was surprised by how easy-to-use and convenient it actually is to use Inertia. In this article I'll talk about how to set up a Laravel - React app using the modern monolith, and the important differences you'll need to know to become an Inertia pro.

🚀 Getting started with Inertia.js

Before you dive in

You'll need to make sure you have following:

Lift off!

  1. Creating a Laravel project in the current directory

    composer create-project laravel/laravel .
    

     

  2. Add breeze (Laravel starterkit), to install React with it later

    composer require laravel/breeze --dev
    

     

  3. Have breeze add React to your project (using Inertia.js)

    php artisan breeze:install react
    

     

  4. Install and run vite & tailwind

    npm i
    npm run dev
    

     

  5. Set up your database settings in .env

    DB_CONNECTION=mysql
    DB_HOST={database host}
    DB_PORT={database port}
    DB_DATABASE={database name}
    DB_USERNAME={database username}
    DB_PASSWORD={database password}
    

     

  6. Migrate the database & start up the server

    php artisan migrate
    php artisan serve
    

     

  7. Pat yourself on the back! 🎊

The office - dance party gif

How to use Inertia to the fullest

Since Inertia will help you combine your favorite frameworks together, there'll be slight changes to how you would use them normally. I have listed most major changes below, but encourage going through the documentation if you'd like the full list, since all of them are described there.

📄 Pages

Just like views in Laravel, you'll have Pages (that you'll render with requests) which are located in the resource/js/Pages/ directory as a .jsx file

An example of what a basic page would look like:

//Index.jsx
const Index = () => {
    return (
        <>
            <h1>Hello World - Index page</h1>
        </>
    );
}
export default Index
Enter fullscreen mode Exit fullscreen mode

Big bang theory - throwing papers around

Rendering a page

A page will be rendered by a route inside routes/web.php. There's two ways to render a page. Both ways, Inertia will go search for the page in the Pages directory by default, and look for {name}.jsx (just like using view() in Laravel).

//web.php
//using the inertia() helper function
Route::get('/', function () {
    return inertia('Index');
    //resources/js/Pages/Index.jsx
});

//or

//using the Inertia object and render method
use Inertia\Inertia;

Route::get('/', function () {
    return Inertia::render('Index');
    //resources/js/Pages/Index.jsx
});
Enter fullscreen mode Exit fullscreen mode

Passing variables to a page

It is possible to pass variables through to the Page in the same way as you can in Laravel.

//web.php
Route::get('/', function () {
    return Inertia::render('Index', [
        'name' => 'Xander'
    ]);
});
Enter fullscreen mode Exit fullscreen mode

The name variable can then be accessed inside the Page file using props as a parameter passed through inside the function, just like in React.

//Index.jsx
const Index = (props) => {
    return (
        <>
            <h1>Hello World - {props.name}</h1>
        </>
    );
}
export default Index
Enter fullscreen mode Exit fullscreen mode

⚓ Links - forget about anchor tags

Anchor tags will reload the page, which will defeat the purpose of making a single-page app using React!

The office creed stating an url gif

The Link component

Inertia will handle anchor tags differently in order to not break the React flow, by using the Link component, which you can import in your Pages or Components wherever needed.

import { Link } from "@inertiajs/react";
Enter fullscreen mode Exit fullscreen mode

Now you can replace all <a> tags with <Link> tags, preventing a page from being reloaded when a visitor clicks on a link.

<nav>
    <ul>
        <li><Link href="/">Home</Link></li>
        <li><Link href="/about">About</Link></li>
        <li><Link href="/contact">Contact</Link></li>
    </ul>
</nav>
Enter fullscreen mode Exit fullscreen mode

🎨 Layouts

If you have code that will be used on most pages, for example the code for the navigation at the top of the pages, you can create a <Layout/> component. While it will be treated as a normal React component, you'll be able to nest the children of the <Layout/> component in any place, and keep your code DRY. This way, you won't have to edit the same code on multiple pages when you want to add a new link to the navigation.

The children parameter passed through to the Layout component will contain any tags and/or components that the <Layout> tag would wrap.

An example of a Layout component:

//Layout.jsx
const Layout = ({children}) => {
    return (
        <>
            <div>
                This is a layout component used on every single page
            </div>
            <div>
                We can choose to show the tags nested inside the
                Layout component tags at any place
            </div>
            <div>
                Here for example:
            </div>
            {children}
            <div>
                This is the end of the layout component
            </div>
        </>
    )
}
export default Layout;
Enter fullscreen mode Exit fullscreen mode
//Index.jsx
import Layout from './Layout';
import Nav from './Components/Nav';
const Index = () => {
    return (
        <>
            <Layout>
                <Nav />
                <p>Extra content</p>
            </Layout>
            <h1>Hello World - Welcome page</h1>
        </>
    );
}
export default Index;
Enter fullscreen mode Exit fullscreen mode

The resulting code for the Index page would be:

<div>
    This is a layout component used on every single page
</div>
<div>
    We can choose to show the tags nested inside the
    Layout component tags at any place
</div>
<div>
    Here for example:
</div>
<!-- start children variable -->
<nav>
    <ul>
        <li><Link href="/">Home</Link></li>
        <li><Link href="/about">About</Link></li>
        <li><Link href="/contact">Contact</Link></li>
    </ul>
</nav>
<p>Extra content</p>
<!-- end children variable -->
<div>
    This is the end of the layout component
</div>
<h1>Hello World - Welcome page</h1>
Enter fullscreen mode Exit fullscreen mode

Persistent layouts

Using the layout as a component works for most cases, but there are a few things to keep in mind while doing so. Since the Layout component is a child of the Page component, every time you render a different page, the Layout component will be destroyed then rebuilt each time.

If you want to prevent this (for several reasons, like performance, or there is a video inside the layout that shouldn't be restarted on each render of another page) you can make use of persistent layouts.

import Layout from './Layout'

const Index = (props) => {
  return (
    <>
      <H1>Welcome</H1>
      <p>Hello {props.user.name}, 
      welcome to your first Inertia app!</p>
    </>
  )
}

Index.layout = page => <Layout children={page} title="Welcome" />

export default Index
Enter fullscreen mode Exit fullscreen mode

✉️ Non-GET requests with Links

If, for example, you want to log a user out, you'll have to do this through a POST request. In Inertia, you are able to make a Link use the POST method instead of the default GET request. When doing this, you'll also have to specify that the Link component will now be used as a button, using the as attribute.

<Link method="POST" as="button" href="/logout">Log out</Link>
Enter fullscreen mode Exit fullscreen mode
//web.php
Route::post("/logout", function() {
    dd("handle logout");
});
Enter fullscreen mode Exit fullscreen mode

Dog going through mail gif

Passing through variables

You'll be able to pass data to the POST request, and receive them the same way as if the POST was made with a form. For this, you can use the data attribute, which will accept an object.

<Link 
    method="POST" 
    data={{foo: "bar"}} 
    as="button" 
    href="/logout"> Log out </Link>
Enter fullscreen mode Exit fullscreen mode
//web.php
Route::post("/logout", function() {
    dd(request('foo')); //"bar"
});
Enter fullscreen mode Exit fullscreen mode

🕵️ Share data across pages & components

If you want data to be accessible on every page/component (for example, the data for a logged in user), you can use the share function inside the Inertia middleware, located in app/Http/Middleware/HandleInertiaRequest.php.

//HandleInertiaRequest.php
public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user() ? $request->user()->only(
                    'id',
                    'name',
                    'email',
                    'birthday',
                    'username',
                ) : null,
            ],
        ]);
    }
Enter fullscreen mode Exit fullscreen mode

To access the shared data, you can use the usePage() hook from Inertia.

//Layout.jsx
import { usePage } from '@inertiajs/react'

export default function Layout({ children }) {
  const { auth } = usePage().props

  return (
    <main>
      <header>
        You are logged in as: {auth.user.name}
      </header>
      <content>
        {children}
      </content>
    </main>
  )
}
Enter fullscreen mode Exit fullscreen mode

When making data globally available throughout the application, it's a good idea to namespace as detailed as possible, to prevent ending up with variables that are named the same. As you can see to access the logged in user with a namespaced variable, you'll have to use auth.user.name, while a user variable (that doesn't have the logged in user assigned) may still be used elsewhere on the page.

⚠️ Never share sensitive information this way. Since this global data will be available client-side, anyone could access all of this data, even when you don't render it on a page.

Secret information gif

🗣️ Head & Title tags

If you want to change the meta tags or titles of your pages, you'll have to use the <Head> component, which Inertia offers us.

import { Head } from '@inertiajs/react'

<Head>
  <title>Your page title</title>
  <meta name="description" content="Your page description" />
</Head>
Enter fullscreen mode Exit fullscreen mode

📃 Changing the title will still add "- Laravel" behind it. This is the app name, defined in the .env file, which you should change to your own.

If you only need to add a title, you can use the shorthand.

import { Head } from '@inertiajs/react'

<Head title="Your page title" />
Enter fullscreen mode Exit fullscreen mode

What if you have multiple Head instances?

Inertia will overwrite the title tag to the one specified inside your specific page component. For meta-tags, we'll need to tell it which ones are the same and should be overwritten by the ones specified inside the page component, by passing along the head-key attribute.

// Layout.js
import { Head } from '@inertiajs/react'

<Head>
  <title>My app</title>
  <meta 
    head-key="description" 
    name="description" 
    content="This is the default description" 
   />
  <link 
    rel="icon" 
    type="image/svg+xml" 
    href="/favicon.svg" 
  />
</Head>
Enter fullscreen mode Exit fullscreen mode
// About.js
import { Head } from '@inertiajs/react'

<Head>
  <title>About - My app</title>
  <meta 
    head-key="description" 
    name="description" 
    content="This is a page specific description" 
  />
</Head>
Enter fullscreen mode Exit fullscreen mode
<!-- RESULT -->
<head>
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
  <title>About - My app</title>
  <meta 
    name="description" 
    content="This is a page specific description" 
  />
</head>
Enter fullscreen mode Exit fullscreen mode

🏝️ Some quality of life features

Inertia offers a few features built-in to help you save time instead of having to write obnoxious/long code.

Dog on the beach vacation gif

Progress bar when loading pages

By default, there is a loading bar at the top of the page whenever the user loads a different page or makes a request.
If you want to disable this loading bar, we can modify the progress property inside the createInertiaApp function, which you can find at resources/js/app.jsx

createInertiaApp({
  progress: false,
  // ...
})
Enter fullscreen mode Exit fullscreen mode

There are several options to customize the look of the progress bar.

createInertiaApp({
  progress: {
    /* The delay after which the progress bar will appear, 
    in milliseconds... */
    delay: 250,

    // The color of the progress bar...
    color: '#29d',

    // Whether to include the default NProgress styles...
    includeCSS: true,

    // Whether the NProgress spinner will be shown...
    showSpinner: false,
  },
  // ...
})
Enter fullscreen mode Exit fullscreen mode

Preventing a page from scrolling up when a request is made

Whenever a link is clicked, and it redirects to the same page, the page will be scrolled back to the top. In Inertia, you can prevent this from happening, by using the preserveScroll attribute inside a <Link/> component

<Link method="POST"
      as="button"
      data={{post-id: 1, user-id: 5}}  
      preserveScroll>
Like the post
</Link>
Enter fullscreen mode Exit fullscreen mode

Styling active links

When the user visits a page, you have the ability to show the user what page they're currently on inside the nav, using conditional classes.

Inertia can check which Page component is currently rendered, and style the link based on that. In order to use this information, you can import the usePage() hook.

import { usePage } from '@inertiajs/react'
const { url, component } = usePage()

//inside the navigation
<Link href="/users" className={component.startsWith('Users') ? 
    'active' :
    ''}>
    Users
</Link>
Enter fullscreen mode Exit fullscreen mode

😎 That's all folks

And that's it, you're now ready to become an Inertia pro! I hope these points will help and make it easier for you to write your first Laravel - React application using Inertia, as much as they helped me and my co-learners. I definitely also recommend the Laracast videos on Inertia, as well as the documentation page itself, for more information.

That's all folks gif

💖 💪 🙅 🚩
xandervdb1
Xandervdb

Posted on June 18, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related