seo: fix canonical tags on post index and show page.

* Fix incorrect canonical tags. Before we were using
  `<meta name="canonical" content="...">`. This is wrong, it should have been
  `<link rel="canonical" href="...">`.

* Add a default canonical tag on all pages. Fixes Google treating the
  same content on different subdomains (safebooru, shima, kagamihara, etc)
  as duplicate content. Also fixes Google sometimes treating similar but
  distinct content on the same domain as duplicate content.
This commit is contained in:
evazion
2020-06-27 19:07:34 -05:00
parent c739e2b226
commit 580211ee64
6 changed files with 49 additions and 10 deletions

View File

@@ -340,6 +340,19 @@ module ApplicationHelper
end end
end end
def canonical_url(url = nil)
if url.present?
content_for(:canonical_url) { url }
elsif content_for(:canonical_url).present?
content_for(:canonical_url)
else
request_params = request.params.sort.to_h.with_indifferent_access
request_params.delete(:page) if request_params[:page].to_i == 1
request_params.delete(:limit)
url_for(**request_params, host: Danbooru.config.hostname, only_path: false)
end
end
def atom_feed_tag(title, url = {}) def atom_feed_tag(title, url = {})
content_for(:html_header, auto_discovery_link_tag(:atom, url, title: title)) content_for(:html_header, auto_discovery_link_tag(:atom, url, title: title))
end end

View File

@@ -5,6 +5,7 @@
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
<%= render "meta_links", collection: @current_item %> <%= render "meta_links", collection: @current_item %>
<%= tag.link rel: "canonical", href: canonical_url %>
<%= csrf_meta_tag %> <%= csrf_meta_tag %>
<% unless CurrentUser.enable_desktop_mode? %> <% unless CurrentUser.enable_desktop_mode? %>

View File

@@ -10,6 +10,10 @@
<% atom_feed_tag "Posts: #{@post_set.tag_string}", posts_url(tags: @post_set.tag_string, format: :atom) %> <% atom_feed_tag "Posts: #{@post_set.tag_string}", posts_url(tags: @post_set.tag_string, format: :atom) %>
<% end %> <% end %>
<% if params[:tags].blank? && @post_set.current_page == 1 %>
<% canonical_url root_url(host: Danbooru.config.hostname) %>
<% end %>
<% if @post_set.hide_from_crawler? %> <% if @post_set.hide_from_crawler? %>
<meta name="robots" content="nofollow,noindex"> <meta name="robots" content="nofollow,noindex">
<% end %> <% end %>
@@ -18,8 +22,6 @@
<meta name="rating" content="adult"> <meta name="rating" content="adult">
<% end %> <% end %>
<%= tag.meta name: "canonical", content: posts_url(tags: params[:tags], host: Danbooru.config.hostname, protocol: "https") %>
<% if @post_set.best_post.present? %> <% if @post_set.best_post.present? %>
<%= tag.meta property: "og:image", content: @post_set.best_post.open_graph_image_url %> <%= tag.meta property: "og:image", content: @post_set.best_post.open_graph_image_url %>
<%= tag.meta name: "twitter:image", content: @post_set.best_post.open_graph_image_url %> <%= tag.meta name: "twitter:image", content: @post_set.best_post.open_graph_image_url %>

View File

@@ -1,7 +1,8 @@
<% page_title @post.presenter.humanized_essential_tag_string %> <% page_title @post.presenter.humanized_essential_tag_string %>
<% meta_description "View this #{@post.image_width}x#{@post.image_height} #{number_to_human_size(@post.file_size)} image" %> <% meta_description "View this #{@post.image_width}x#{@post.image_height} #{number_to_human_size(@post.file_size)} image" %>
<% canonical_url post_url(@post, host: Danbooru.config.hostname) %>
<% atom_feed_tag "Comments for post ##{@post.id}", comments_url(:atom, search: { post_id: @post.id }) %> <% atom_feed_tag "Comments for post ##{@post.id}", comments_url(:atom, search: { post_id: @post.id }) %>
<%= render "posts/partials/common/secondary_links" %> <%= render "posts/partials/common/secondary_links" %>
<% content_for(:sidebar) do %> <% content_for(:sidebar) do %>
@@ -148,8 +149,6 @@
<%= tag.meta property: "og:image", content: @post.open_graph_image_url %> <%= tag.meta property: "og:image", content: @post.open_graph_image_url %>
<% end %> <% end %>
<%= tag.meta name: "canonical", content: post_url(@post, host: Danbooru.config.hostname, protocol: "https") %>
<% if @post.twitter_card_supported? %> <% if @post.twitter_card_supported? %>
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">

View File

@@ -1,6 +1,10 @@
require "test_helper" require "test_helper"
class PostsControllerTest < ActionDispatch::IntegrationTest class PostsControllerTest < ActionDispatch::IntegrationTest
def assert_canonical_url_equals(expected)
assert_equal(expected, response.parsed_body.css("link[rel=canonical]").attribute("href").value)
end
context "The posts controller" do context "The posts controller" do
setup do setup do
@user = travel_to(1.month.ago) {create(:user)} @user = travel_to(1.month.ago) {create(:user)}
@@ -10,11 +14,29 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
context "index action" do context "index action" do
setup do setup do
mock_post_search_rankings(Date.today, [["1girl", 100], ["original", 50]]) mock_post_search_rankings(Date.today, [["1girl", 100], ["original", 50]])
create_list(:post, 2)
end end
should "render" do context "for an empty search" do
get posts_path should "render the first page" do
assert_response :success get root_path
assert_response :success
assert_canonical_url_equals(root_url(host: Danbooru.config.hostname))
get posts_path
assert_response :success
assert_canonical_url_equals(root_url(host: Danbooru.config.hostname))
get posts_path(page: 1)
assert_response :success
assert_canonical_url_equals(root_url(host: Danbooru.config.hostname))
end
should "render the second page" do
get posts_path(page: 2, limit: 1)
assert_response :success
assert_canonical_url_equals(posts_url(page: 2, host: Danbooru.config.hostname))
end
end end
context "with a single tag search" do context "with a single tag search" do
@@ -22,6 +44,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
get posts_path, params: { tags: "does_not_exist" } get posts_path, params: { tags: "does_not_exist" }
assert_response :success assert_response :success
assert_select "#show-excerpt-link", count: 0 assert_select "#show-excerpt-link", count: 0
assert_canonical_url_equals(posts_url(tags: "does_not_exist", host: Danbooru.config.hostname))
end end
should "render for an artist tag" do should "render for an artist tag" do
@@ -261,7 +284,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
get posts_path(format: :atom) get posts_path(format: :atom)
assert_response :success assert_response :success
assert_select "entry", 1 assert_select "entry", 3
end end
should "render with tags" do should "render with tags" do
@@ -272,7 +295,7 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
end end
should "hide restricted posts" do should "hide restricted posts" do
@post.update(is_banned: true) Post.update_all(is_banned: true)
get posts_path(format: :atom) get posts_path(format: :atom)
assert_response :success assert_response :success

View File

@@ -64,6 +64,7 @@ class ActionDispatch::IntegrationTest
extend ControllerHelper extend ControllerHelper
register_encoder :xml, response_parser: ->(body) { Nokogiri.XML(body) } register_encoder :xml, response_parser: ->(body) { Nokogiri.XML(body) }
register_encoder :html, response_parser: ->(body) { Nokogiri.HTML5(body) }
def method_authenticated(method_name, url, user, **options) def method_authenticated(method_name, url, user, **options)
post session_path, params: { name: user.name, password: user.password } post session_path, params: { name: user.name, password: user.password }