Application level improvements

Application level improvements

·

4 min read

Layouts

For the moment, we have just one Twill module with one Inertia page, but we will probably have more page templates with different modules in our application (home, blog, portfolio, contact, customer account, ...).

It may be a good idea to create one (or more) layouts that will be used by our different pages and will be persistent (not destroyed and recreated every visit). For this article, we will create 2 layouts and see how to use it on our pages:

  • a Default for the common pages (which may have a header with a menu, a footer, ...)

  • a FullPage for custom pages (like authentication)

Layouts creation

We create our 2 templates in a new directory /resources/views/Components/Layout:

/resources/views/Components/Layout/Default.vue

<template>
  <div class="min-h-full">
    <header class="bg-white shadow">
      <div class="mx-auto max-w-7xl px-4 py-6">
        <h1 class="text-3xl font-bold tracking-tight text-gray-900">My Application</h1>
      </div>
    </header>
    <main>
      <div class="mx-auto max-w-7xl py-6">
        <slot></slot>
      </div>
    </main>
  </div>
</template>

/resources/views/Components/Layout/FullPage.vue

<template>
  <main class="w-full flex flex-col h-screen content-center justify-center">
    <slot></slot>
  </main>
</template>

Configuration

For the moment, our layouts are just Vue components, to make Inertia aware of the layout every page has to load, we need to do some changes in our application entry point in the resolve() method:

/resources/js/app.ts

before:

  resolve: async (name: string) => {
    let page = await resolvePageComponent(`../views/Pages/${name}.vue`, import.meta.glob<DefineComponent>('../views/Pages/**/*.vue'))

    return page
  },

after:

  resolve: async (name: string) => {
    let page = await resolvePageComponent(`../views/Pages/${name}.vue`, import.meta.glob<DefineComponent>('../views/Pages/**/*.vue'))

    page = page.default

    page.layout = (await import(`../views/Components/Layout/${page.layoutName || 'Default'}.vue`)).default

    return page
  },

What it does

To set the page.layout value, it will

  • look if the page has a layoutName option defined and then load the Component with that name which would be in /resources/views/Components/Layout directory

  • if not, load the Default layout

More info in the official documentation.

We don't test if the layout Component file exists, if a wrong name is defined, it will trigger an error:

Define the Layout of the page

Now we can choose our layout on each page.

If you use the unplugin-vue-define-options plugin, it can be done like that:

/resources/views/Pages/Page/Content.vue

<script setup lang="ts">
defineOptions({
  layoutName: 'FullPage',
})

interface Props {
  item: Model.Page
  locale: string
}

defineProps<Props>()
</script>

If not, we need to use Option API like that:

/resources/views/Pages/Page/Content.vue

<script lang="ts">
export default {
  layoutName: 'FullPage',
}
</script>

<script setup lang="ts">
interface Props {
  item: Model.Page
  locale: string
}

defineProps<Props>()
</script>

You should see:

And if you remove the configuration or set layoutName to Default, you should see:

Head

In Web applications, it's a good practice to define a page title, and maybe meta tags (like description). Inertia provides a Head component that helps us for this purpose. More info in the official documentation.

Our Head component

To have a better organization and more flexibility, we will create our Head component that will extend the Inertia's Head component.

/resources/views/Components/Theme/Head.vue

<script setup lang="ts">
import { Head as InertiaHead } from '@inertiajs/vue3'

defineOptions({
  name: 'ThemeHead',
})

interface Props {
  item?: any
  title?: string
  description?: string
}

const props = withDefaults(defineProps<Props>(), {
  item: () => ({}),
  title: '',
  description: '',
})

const appName = 'My Application'

const title =
  props.item && props.item.meta_title && props.item.meta_title != ''
    ? props.item.meta_title
    : props.item && props.item.title && props.item.title != ''
    ? props.item.title
    : props.title

const description =
  props.item && props.item.meta_description && props.item.meta_description != ''
    ? props.item.meta_description
    : props.item && props.item.description && props.item.description != ''
    ? props.item.description
    : props.description
</script>

<template>
  <InertiaHead :title="title ? `${title} | ${appName}` : appName">
    <meta
      head-key="description"
      name="description"
      :content="description"
    />
  </InertiaHead>
</template>

What it does

The component defines title and meta-description tags from props

  • if an item prop is provided (from PageContent, PageHome, BlogArticle, ...), it looks for:

    • meta_title and meta_description item properties

    • then title and description properties if not defined

  • if not, looks for title and description props

The title is filled out by the appName variable (here it is static text My Application, you can make it dynamic or add your logic according to your project needs).

Usage

/resources/views/Pages/Page/Content.vue

<script setup lang="ts">
import Head from '@Theme/Head.vue'

...
</script>

<template>
  <Head :item="item"></Head>
  ...
</template>

If we go back to our Twill module, if we define the SEO information:

They will be rendered on the HTML page:


We'll do our best to provide source code of the serie on GitHub