-
Notifications
You must be signed in to change notification settings - Fork 67
Server driven meta tags #240
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
base: master
Are you sure you want to change the base?
Conversation
…o let's camel case it
…instead of kebab-case
… goes inside a non self-closing tag
…class methods. Instance methods remove complexity and enable better control of the order of operations.
… page object so we don't depend on changes to Inertia.js
:::tabs key:frameworks | ||
== Vue | ||
|
||
```vue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested all of these components in shiny new Inertia Rails apps. That said, I've never shipped production Vue/Svelte code. Extra eyes would be appreciated
> If you have a `<title>` tag in your Rails layout, make sure it has the `inertia` attribute on it so Inertia knows it should deduplicate it. The Inertia Rails generator does this for you automatically. | ||
|
||
|
||
### Client Side |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about releasing this in a separate package or trying to merge this into Inertia.js core, but decided the path of least resistance was just to add instructions to copy paste the (tiny) component required to make this work.
|
||
## Rendering Meta Tags | ||
|
||
Tags are defined as plain hashes and conform to the following structure: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked at the Rails meta_tags gem and Remix.run's meta objects for inspiration. I decided to minimize "magic". The data structure just describes an HTML, with a few exceptions:
tag_name
will default to:meta
if it isn't definedinner_content
is used for non-void tagstitle
has shortcut syntax
end | ||
``` | ||
|
||
#### The `inertia_meta` API |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is basically the API I wish we had built for inertia_share
. If you look at the commit history, I actually built out an entire implementation with inertia_meta
as a controller class method, before remembering how much I regret doing that for inertia_share
😅
document.head | ||
.querySelectorAll('[inertia]') | ||
.forEach(el => el.remove()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually a lot simpler implementation than the React/Vue adapters have. Since you don't have to worry about built in <Head />
components defining their own meta tags you can safely clear all the server rendered tags and re-render them from scratch.
@@ -2,6 +2,9 @@ | |||
|
|||
Since Inertia powered JavaScript apps are rendered within the document `<body>`, they are unable to render markup to the document `<head>`, as it's outside of their scope. To help with this, Inertia ships with a `<Head>` component which can be used to set the page `<title>`, `<meta>` tags, and other `<head>` elements. | |||
|
|||
> [!NOTE] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this isn't a part of the Inertia.js core, I think it's better to talk about this in a cookbook instead of inside the main title/meta page.
@@ -103,7 +103,7 @@ def install_inertia | |||
before: '<%= vite_client_tag %>' | |||
end | |||
|
|||
gsub_file application_layout.to_s, /<title>/, '<title inertia>' unless svelte? | |||
gsub_file application_layout.to_s, /<title>/, '<title inertia>' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Svelte component from the doc above needs the inertia tag to properly dedup so I removed this.
Digest::MD5.hexdigest(signature)[0, 8] | ||
end | ||
|
||
def generate_meta_head_key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this after testing it in a real app based on feedback from our team. It seemed unncessary to have to add head_key: "description" everywhere when you know you want to dedup
name: "description"` 100% of the time.
We dedup on the the values in :name
, :property
and :http_equiv
automatically.
lib/inertia_rails/meta_tag.rb
Outdated
def handle_script_content(content) | ||
case content | ||
when String | ||
@raw ? content.html_safe : ERB::Util.html_escape(content) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an (undocumented) escape hatch in case you want to render eval'able <script>
tags. It's undocumented because I can't think of a good reason why you'd actually want to render a script tag like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The html_safe and dangerouslySet make me a little nervous. At first glance I can't think of how someone could abuse those though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I dug back into my thinking and am going to make a couple changes. I don't think we need eval'able scripts after reconsidering it (I was originally thinking about analytics tracking scripts, but that doesn't really belong in this feature). And I need to escape the content on Inertia requests... the browser normally ignores script content created by dangerouslySetInnerHTML
, but Inertia.js's <Head>
renderer overrides it and make it eval'able again.
@meta_tags << new_tag | ||
end | ||
|
||
def duplicate?(existing_tag, new_tag) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the duplication logic is in the head_key generation. Here we just check that, with a special second case for the title
tag.
Deploying inertia-rails with
|
Latest commit: |
d91ad71
|
Status: | ✅ Deploy successful! |
Preview URL: | https://8413f0fb.inertia-rails.pages.dev |
Branch Preview URL: | https://server-driven-meta-tags.inertia-rails.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a server-side meta tag feature to Inertia Rails, allowing pages to define and render meta tags without requiring JS SSR.
- Introduces
MetaTag
andMetaTagBuilder
for tag creation, deduplication, and head-key generation - Updates the
Renderer
and adds ainertia_meta_tags
helper to merge meta into Inertia props and render in the layout - Provides new specs, dummy controller routes, and docs/generator updates for the server-driven meta tags workflow
Reviewed Changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
lib/inertia_rails/meta_tag.rb | Defines MetaTag behavior (JSON, HTML rendering, key gen) |
lib/inertia_rails/meta_tag_builder.rb | Implements MetaTagBuilder to add/remove/clear meta tags |
lib/inertia_rails/renderer.rb | Extends renderer to accept meta: and merge into page props |
lib/inertia_rails/helper.rb | Adds inertia_meta_tags helper for rendering tags in <head> |
lib/inertia_rails/controller.rb | Adds inertia_meta method to controllers |
lib/inertia_rails.rb | Passes meta option through the top‐level inertia method |
spec/inertia/meta_tag_spec.rb | Unit tests for MetaTag#to_json and head-key generation |
spec/inertia/meta_tag_rendering_spec.rb | Request specs for meta tags in Inertia JSON responses |
spec/inertia/helper_spec.rb | Helper tests for inertia_meta_tags |
spec/dummy/config/routes.rb | Adds dummy routes for all meta tag scenarios |
spec/dummy/app/controllers/inertia_meta_controller.rb | Dummy controller to drive meta tag examples |
spec/dummy/app/controllers/concerns/meta_taggable.rb | Concern for setting shared default meta |
lib/generators/inertia/install/install_generator.rb | Tweaks to always inject <title inertia> |
lib/inertia_rails/generators/helper.rb | Renames guess_typescript to guess_typescript? |
lib/inertia_rails/generators/controller_template_base.rb | Updates default typescript option to use guess_typescript? |
docs/guide/title-and-meta.md | Notes the new Rails‐managed meta tag support |
docs/cookbook/server-managed-meta-tags.md | Full cookbook for server-driven meta tags |
docs/.vitepress/config.mts | Registers the cookbook in nav |
Comments suppressed due to low confidence (2)
docs/cookbook/server-managed-meta-tags.md:5
- [nitpick] Consider hyphenating 'server defined' to 'server-defined' for clarity and consistency.
Inertia Rails renders server defined meta tags into both the server rendered HTML and the client-side Inertia page props. Because the tags share unique `head-key` attributes, the client will "take over" the meta tags after the initial page load.
lib/inertia_rails/renderer.rb:95
- [nitpick] Add unit tests for
computed_props
(including_inertia_meta
) to verify that meta tags are merged correctly when provided or absent.
deep_transform_props(merged_props).merge({
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is awesome! About time you upstreamed it 😉
Docs look great at a glance, but I'm pretty tired right now so I want to give the implementation a second look when I have more brainpower to give it a proper review.
…marked text/plain
Co-authored-by: Copilot <[email protected]>
@bknoles Is it backward-compatible when we use the syntax that omitting the component name with props on render please? |
This is a feature I've wanted to add for a long time now. We built an ad hoc version of it ~3 years ago and have been using it in prod since then.
The biggest benefit to this implementation: per-page link previews in a React/Vue/Svelte app without JS SSR required.
The page I added to the docs is comprehensive, but as a quick preview, by adding a single line to the Rails layout and a copying a small component client-side, you can now do:
Inertia Rails will render a meta tag in the server rendered HTML that Inertia.js will pickup and "take over" rendering during Inertia visits.
There's also an
inertia_meta
controller instance method, that lets you share tags across actions:Inertia Rails intelligently deduplicates tags, so you can easily set and override defaults. Plus it uses the same
head_key
as Inertia.js if you want/need more control.Test checklist/Todos