Application level improvements

Search for a command to run...

No comments yet. Be the first to comment.
Our attempt to build a CMS using Laravel and Twill for the back-end and Inertia with Vite, Vue 3 Composition API, TypeScript and Tailwind for the front-end.
In this article, we will see the creation and customization of a Twill singleton module to provide a Homepage for our application. Singleton module creation In Twill terminology, a Singleton Module is globally like a classic module except that it man...
Now that we have set up all the tools to create blocks on both Twill and Vue sides, let's create some generic blocks. Each block will be created in the Common namespace and will be manually added as an allowed block in the controller of our PageConte...

Now that we can create pages, it's time to leverage Twill's powerful content management features and construct reusable blocks. In this article, we will focus on creating a basic Title Twill Block (with a translatable Twill Text Input in a Common nam...

You are probably already familiar with the work of Spatie and its contributors in the Laravel and Open Source ecosystem. For many years, we have been using their Backup package (https://spatie.be/docs/laravel-backup) to back up our applications (data...

If a Twill module uses Revisions, you can benefit from an awesome Twill feature: preview your changes before publishing and compare old revisions. By default, it uses Blade views, so working with Inertia needs some adjustments. The principle For now,...

In this article, we will see the creation and customization of a Twill singleton module to provide a Homepage for our application. Singleton module creation In Twill terminology, a Singleton Module is globally like a classic module except that it man...

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)
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>
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:
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:

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.
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).
/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