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

Retreive head data #455

Open
1 task
mrleblanc101 opened this issue Jan 10, 2025 · 2 comments
Open
1 task

Retreive head data #455

mrleblanc101 opened this issue Jan 10, 2025 · 2 comments
Labels
enhancement New feature or request

Comments

@mrleblanc101
Copy link

mrleblanc101 commented Jan 10, 2025

Describe the feature

Currently, if I set the title in my Pages using useSeoMeta() or useHead().
I have no way of accessing this value inside my Layouts.

// pages/index.vue
<script setup>
useSeoMeta({
    title: () => t('Internationalization'),
});
</script>

To work around this, I use definePageMeta.
But this requires me to move my translations to the global scope.

// pages/index.vue
<script setup>
definePageMeta({
    title: 'Internationalization',
});
useSeoMeta({
    title: () => $t('Internationalization'),
});
</script>
// layouts/default.vue
<template>
    <div>
        <h1>{{ $t($route.meta.title) }}</h1>
    </div>
</template>

Maybe useHead() with no arguments should return the head object like so:

// layouts/default.vue
<script setup>
const head = useHead()
</script>

<template>
    <div>
        <h1>{{ head.title }}</h1>
    </div>
</template>

The alternative would be to use useState or a pinia store, but maybe this should be baked-in.
If this is implemented, there should be a way to get both the original title, and the title after ran into titleTemplate.
For exemple, with this config:

// nuxt.config.js
app: {
    head: {
        title: 'Home,
        titleTemplate: `%s %separator %siteName`,
        templateParams: {
            siteName: 'Site Name',
        },
    },
},

Maybe:

head.renderedTitle = Home - Site Name 
head.originalTitle = Home
head.title = ?? // Not sure if `head.title` should return what is shown in the `<title>` tag (after titleTemplate), or the property `title` of useHead / useSeoMeta (before titleTemplate)

Additional information

  • Would you be willing to help implement this feature?
@mrleblanc101 mrleblanc101 added the enhancement New feature or request label Jan 10, 2025
@mrleblanc101
Copy link
Author

mrleblanc101 commented Jan 13, 2025

My current workaround.
I created a custom useTitle() composable:

export default function (title: string) {
    const state = useState('state', () => '');

    if (title) {
        state.value = title;

        useSeoMeta({
            title,
        });
    }

    return { title: state };
}

So in my page I do:

<script lang="ts" setup>
useTitle(t('My Page Title'));
</script>

And in my AppHeader, I do:

<script lang="ts" setup>
const { title } = useTitle()
</script>

<template>
    <header>
        ...
        <h1>{{ title }}</h1>
        ...
    </header>
</template>

@harlan-zw
Copy link
Collaborator

harlan-zw commented Jan 14, 2025

The specific issue in why the head data can't just be retrieved immediately is that it's resolved async after the component tree is finished updating. If it was to give you all of the data that was rendered it could lead to recursive reactivity issues or just triggering flashes of the title.

The solution you have here is good, Nuxt Scripts exports a similar function to use the title but for the context of analytic reporting.

Not too sure how else to solve this at the Unhead level while it's async (running it sync will be a big performance loss and lead to reactivity issues).

export function useScriptEventPage(onChange?: (payload: TrackedPage) => void) {
  const nuxt = useNuxtApp()
  const route = useRoute()
  const head = injectHead()
  const payload = ref<TrackedPage>({
    path: route.fullPath,
    title: import.meta.client ? document.title : '',
  })
  // no know to know the title on the server until the page is rendered
  if (import.meta.server)
    return payload

  let lastPayload: TrackedPage = { path: '', title: '' }
  let stopDomWatcher = () => {}
  // TODO make sure useAsyncData isn't running
  nuxt.hooks.hook('page:finish', () => {
    Promise.race([
      // possibly no head update is needed
      new Promise(resolve => setTimeout(resolve, 100)),
      new Promise<void>((resolve) => {
        stopDomWatcher = head.hooks.hook('dom:rendered', () => resolve())
      }),
    ])
      .finally(stopDomWatcher)
      .then(() => {
        payload.value = {
          path: route.fullPath,
          title: document.title,
        }
        if (lastPayload.path !== payload.value.path || lastPayload.title !== payload.value.title) {
          if (onChange) {
            onChange(payload.value)
          }
          lastPayload = payload.value
        }
      })
  })
  return payload
}

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

No branches or pull requests

2 participants