diff --git a/app/helpers/better_together/application_helper.rb b/app/helpers/better_together/application_helper.rb index 7470d9d88..5f8e2942b 100644 --- a/app/helpers/better_together/application_helper.rb +++ b/app/helpers/better_together/application_helper.rb @@ -160,6 +160,25 @@ def open_graph_meta_tags # rubocop:todo Metrics/AbcSize, Metrics/MethodLength, M # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity + # Generates a canonical link tag for the current request. + # Defaults to request.original_url but can be overridden by setting + # `content_for(:canonical_url)` in views. When provided a relative path, + # the host and locale are ensured by prefixing with `base_url_with_locale`. + def canonical_link_tag + canonical_url = if content_for?(:canonical_url) + content_for(:canonical_url) + else + request.original_url + end + + unless canonical_url.starts_with?('http://', 'https://') + path = canonical_url.sub(%r{^/#{I18n.locale}}, '') + canonical_url = "#{base_url_with_locale}#{path}" + end + + tag.link(rel: 'canonical', href: canonical_url) + end + # Retrieves the setup wizard for hosts or raises an error if not found. # This is crucial for initial setup processes and should be pre-configured. def host_setup_wizard diff --git a/app/views/layouts/better_together/application.html.erb b/app/views/layouts/better_together/application.html.erb index 5a40356a7..91be40036 100644 --- a/app/views/layouts/better_together/application.html.erb +++ b/app/views/layouts/better_together/application.html.erb @@ -12,6 +12,7 @@ <%= (yield(:page_title) + ' | ') if content_for?(:page_title) %><%= host_platform.name %> <%= open_graph_meta_tags %> <%= seo_meta_tags %> + <%= canonical_link_tag %> <%= robots_meta_tag %> diff --git a/app/views/layouts/better_together/turbo_native.html.erb b/app/views/layouts/better_together/turbo_native.html.erb index 68f193a01..d1f9c5ab7 100644 --- a/app/views/layouts/better_together/turbo_native.html.erb +++ b/app/views/layouts/better_together/turbo_native.html.erb @@ -12,6 +12,7 @@ <%= (yield(:page_title) + ' | ') if content_for?(:page_title) %><%= host_platform.name %> <%= open_graph_meta_tags %> <%= seo_meta_tags %> + <%= canonical_link_tag %> <%= robots_meta_tag %> diff --git a/spec/helpers/better_together/application_helper_spec.rb b/spec/helpers/better_together/application_helper_spec.rb index 735112991..3abc666e8 100644 --- a/spec/helpers/better_together/application_helper_spec.rb +++ b/spec/helpers/better_together/application_helper_spec.rb @@ -3,7 +3,37 @@ require 'rails_helper' module BetterTogether - RSpec.describe ApplicationHelper do + RSpec.describe ApplicationHelper, type: :helper do + describe '#canonical_link_tag' do + before do + allow(helper).to receive(:base_url_with_locale).and_return('https://example.com/en') + end + + context 'when no canonical_url is provided' do + it 'defaults to request.original_url' do + allow(helper.request).to receive(:original_url).and_return('https://example.com/en/posts') + result = helper.canonical_link_tag + expect(result).to include('href="https://example.com/en/posts"') + end + end + + context 'when canonical_url is a relative path with locale' do + it 'prefixes base_url_with_locale and removes duplicate locale' do + helper.content_for(:canonical_url, '/en/custom') + result = helper.canonical_link_tag + expect(result).to include('href="https://example.com/en/custom"') + end + end + + context 'when canonical_url is a full URL' do + it 'uses the provided URL' do + helper.content_for(:canonical_url, 'https://external.test/path') + result = helper.canonical_link_tag + expect(result).to include('href="https://external.test/path"') + end + end + end + describe '#robots_meta_tag' do it 'renders default robots meta tag' do # rubocop:todo RSpec/MultipleExpectations tag = helper.robots_meta_tag