inertia
inertia copied to clipboard
[2.x] Implement unhead for Vue 3 and its playground
The current SSR / Head implementation is imho lacking some features and it's a bit cumbersome to configure. I started experimenting with unhead, which is a framework-agnostic implementation and has quite a few nice features.
Using this plugin is still rather similar compared to the current implementation, as the Vue implementation offers a component to be used in templates. Small downside is that there aren't component implementation for React / Svelte, just the composable. Personally I like this approach way more, and seems to be a common approach in React, but I'm not sure about Svelte.
This would however require either user-implementation or providing an Inertia wrapper, since items pushed to the head would need to be disposed when the component unmounts. The @unhead/vue package does this, but the bare unhead package doesn't have this.
[!NOTE]
This PR isn't finished yet, but hopefully provides a good indication of the benefits of using unhead If there's interest in a full implementation I would love to continue on this
Client implementation / usage
Setup in Vue is rather similar to how you'd set up unhead in a regular Vue app;
import { createInertiaApp } from '@inertiajs/vue3'
import { createSSRApp, h, type DefineComponent } from 'vue'
import { createHead, useHead } from '@unhead/vue'
createInertiaApp({
setup({ el, App, props, plugin }) {
const head = createHead()
useHead({
titleTemplate: (title) => `${title} - Vue 3 Playground`,
})
createSSRApp({ render: () => h(App, props) })
.use(plugin)
.use(head)
.mount(el)
},
})
In ssr.js this approach is the same. Not the missing title callback, as that's now being dealt with by unhead. It provides the same functionality as what Inertia provided, along with the ability to use a template.
In a page component you'd simply call the useHead composable;
<script setup lang="ts">
import { useHead } from '@unhead/vue';
useHead({
title: 'Article'
})
</script>
Server rendering
The server rendering is written in a backwards-compatible way, it will now respond with the following type:
{
head: string[]
body: string
unhead: {
headTags: string
bodyTags: string
bodyTagsOpen: string
htmlAttrs: string
bodyAttrs: string
}
}
This way the @inertiajs/inertia-laravel implementation can be updated independently. The head will now contain head-tags rendered by unhead (skipping htmlAttrs and bodyAttrs).
For the backend implementation I'd suggest something like the following, similar to what the unhead docs suggest:
<!DOCTYPE html>
<html>
<head @inertiaHead('htmlAttrs')>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
@vite('resources/js/app.js')
@inertiaHead('headTags')
</head>
<body @inertiaHead('bodyAttrs')>
@inertiaHead('bodyTagsOpen')
@inertia
@inertiaHead('bodyTags')
</body>
</html>
The current implementation in Inertia
The current implementation in Inertia has a few downsides, which I've often ran into
- An
inertiaattribute is added to every tag managed by Inertia. This is a non-standard attribute, lots of checkers complain about this. Not sure if this is allowed in the HTML spec, but also IDEs complain about this. Usingdata-inertiamight be better, but I'd rather see this not being used at all. Unhead does it properly without using an arbitrary attribute. - Adding things like JSON+LD to the head would currently require lots of workarounds or weird render methods (
<component is="script">), with unhead this sort of data is now a lot easier
Next steps
React / Svelte
To be able to use this in React / Svelte it would be beneficial to offer unhead through a wrapper. This way Inertia has full control over the lifecycle. The @unhead/vue wrapper has some niceties such as supporting refs / computed as input. Harlan Wilton did mention on Discord the plan for v2 of unhead is to support more frameworks. I think Inertia can provide a simple wrapper, and when support for other frameworks land we can just make use of that.
Managing the current head
In my current implementation I took a rather simple approach; let the user create a head instance, then during SSR we'd just get that instance through getActiveHead(). This might be a bit of a naive approach, it might be better to let Inertia provide this, then users would just consume from it. I think that would also be pretty much solved if we'd provide a wrapper/composable that people can use