Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2.x] Implement unhead for Vue 3 and its playground #1928

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

RobertBoes
Copy link
Contributor

@RobertBoes RobertBoes commented Jul 21, 2024

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 inertia attribute 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. Using data-inertia might 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant