blog.podqueue.fm PodQueue - "Listen Later" for audio https://blog.podqueue.fm/ Wed, 18 Mar 2026 20:24:46 +0000 Wed, 18 Mar 2026 20:24:46 +0000 Jekyll v3.10.0 New PodQueue Premium feature: Pinboard integration <p><a href="https://podqueue.fm/pages/pricing">PodQueue Premium</a> now supports automatically synchronizing all of your saved links to the third-party bookmarking service <a href="https://pinboard.in">Pinboard</a>!</p> <p>Any existing links you have saved in PodQueue will be synchronized with their original save date when you first configure the integration, and any new links will automatically be saved to Pinboard as well, with an optional tag you can specify. Synchronization uses your Pinboard “add bookmarks as private by default” setting to determine if links saved to Pinboard are saved as public or private.</p> <p>You can configure your Pinboard integration settings for PodQueue by going to <a href="https://podqueue.fm/users/pinboard">“Pinboard Integration” in your PodQueue account settings</a>.</p> Sat, 13 Aug 2022 00:00:00 +0000 https://blog.podqueue.fm/2022/08/13/new_podqueue_premium_feature_pinboard_integration/ https://blog.podqueue.fm/2022/08/13/new_podqueue_premium_feature_pinboard_integration/ No, You Don't Need a Cookie Popup on Your Site <hr /> <p><strong>TL;DR:</strong> No, you don’t need a cookie popup on your site. Yes, you do need to comply with the GDPR and the ePrivacy Directive.</p> <hr /> <p>A frequent question for people starting a website these days is: “<a href="https://www.indiehackers.com/post/do-you-use-a-cookie-popup-on-your-website-9357919268">Don’t I need a cookie popup?</a>”</p> <p>With cookie consent notifications seemingly everywhere, it’s a reasonable question. But just because other websites use cookie popups, doesn’t mean yours has to.</p> <p>When I was building <a href="https://podqueue.fm">PodQueue</a>, I wanted to be extremely sensitive to user privacy. <a href="https://blog.podqueue.fm/2021/08/29/going_cookie-free_with_rails/">I even wrote up a post about how to use Rails without setting session cookies</a>. As it turns out though, <strong>as long as you’re willing to exclusively use certain kinds of first-party cookies</strong>, you do not need a cookie popup.</p> <p>Don’t just take my word for it, though. Look at the regulations, the explanations, and <a href="https://www.iccl.ie/news/gdpr-enforcer-rules-that-iab-europes-consent-popups-are-unlawful/">the ongoing enforcement of cookie consent policies</a>. <a href="https://gdpr.eu/cookies/">GDPR.eu has a very good explainer article here</a>, and the most important point for you if you want to avoid using a cookie popup is this:</p> <blockquote> <p>Receive users’ consent before you use any cookies <strong>except</strong> strictly necessary cookies.</p> </blockquote> <p>If you’re only using strictly necessary cookies, you don’t need a cookie popup. So, what are “strictly necessary cookies”?</p> <blockquote> <p>Strictly necessary cookies — These cookies are essential for you to browse the website and use its features, such as accessing secure areas of the site. Cookies that allow web shops to hold your items in your cart while you are shopping online are an example of strictly necessary cookies. These cookies will generally be first-party session cookies.</p> </blockquote> <p>The linked article then goes on to contrast these with other types of cookies (preferences, statistics, and marketing), but the important takeaway is <strong>as long as you only use first-party session cookies, you don’t need a cookie popup</strong>.</p> <p>Yes, this means you need to read up on the other kinds of cookies and ensure you’re not using them. Yes, this may have technical implications for how you design your website. But the payoffs—respecting your users’ privacy and avoiding annoying popups—are well worth the investment.</p> <hr /> <p><em>Disclaimer: I am not a lawyer, and nothing in this post should be taken as legal advice.</em></p> Sat, 02 Apr 2022 00:00:00 +0000 https://blog.podqueue.fm/2022/04/02/no_you_dont_need_a_cookie_popup_on_your_site/ https://blog.podqueue.fm/2022/04/02/no_you_dont_need_a_cookie_popup_on_your_site/ Flexible Passwordless Rails Authentication with <code>devise-passwordless</code> <p>For <a href="https://podqueue.fm">PodQueue</a>, I wanted users to be able to sign up with <em>just</em> an email address, instead of having to also pick a username and password at sign-up time. You’ve probably seen this strategy with some other online services like Slack, where the login process can be handled by a so-called “magic link” that gets emailed to the email address associated with your account, and clicking the link logs you in. The problem is, I already had users with passwords, and I still wanted to support password-based authentication for users who want it—the passwordless login flow is just available for users who find that easier.</p> <p>You may be concerned that passwordless email-based login is “insecure”, but if you allow for email-based account/password recovery (e.g. <a href="https://www.rubydoc.info/github/plataformatec/devise/master/Devise/Models/Recoverable">the Devise <code class="language-plaintext highlighter-rouge">:recoverable</code> strategy</a>), <em>you already have an email-based login process even if you don’t call it that</em>.</p> <p>I was already using <a href="https://github.com/heartcombo/devise">Devise</a> for authentication, and settled on the <a href="https://github.com/abevoelker/devise-passwordless"><code class="language-plaintext highlighter-rouge">devise-passwordless</code> gem</a> to provide the core of my passwordless authentication strategy. Out of the box, <code class="language-plaintext highlighter-rouge">devise-passwordless</code> assumes you’re <em>only</em> going to use a passwordless authentication strategy, but with a little work you can adapt it to work flexibly alongside password-based authentication. This assumes you’ve already set up Devise, and followed the default install intructions for <code class="language-plaintext highlighter-rouge">devise-passwordless</code> for your Devise resources. The only resource I’m using Devise for is my <code class="language-plaintext highlighter-rouge">User</code> model, which I assume will be the case for most other projects as well, but obviously you’ll need to adapt things for your particular goals and Devise configuration.</p> <p>I also assume you have templates generated for overriding your Devise controllers, with e.g.:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails generate devise:controllers devise/users </code></pre></div></div> <p>You should now have overridable Devise controllers in <code class="language-plaintext highlighter-rouge">app/controllers/devise/users/</code> and <code class="language-plaintext highlighter-rouge">app/controllers/devise/devise-passwordless/</code>. Your <code class="language-plaintext highlighter-rouge">config/routes.rb</code> should have a Devise configuration like the following:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">devise_for</span> <span class="ss">:users</span><span class="p">,</span> <span class="ss">controllers: </span><span class="p">{</span> <span class="ss">registrations: </span><span class="s1">'devise/users/registrations'</span><span class="p">,</span> <span class="ss">sessions: </span><span class="s1">'devise/passwordless/sessions'</span> <span class="p">}</span> <span class="n">devise_scope</span> <span class="ss">:user</span> <span class="k">do</span> <span class="n">get</span> <span class="s1">'/users/magic_link'</span><span class="p">,</span> <span class="ss">to: </span><span class="s1">'devise/passwordless/magic_links#show'</span><span class="p">,</span> <span class="ss">as: </span><span class="s1">'users_magic_link'</span> <span class="k">end</span> </code></pre></div></div> <p>You then need to override the default <code class="language-plaintext highlighter-rouge">devise-passwordless</code> sessions controller at <code class="language-plaintext highlighter-rouge">app/controllers/devise/devise-passwordless/sessions_controller.rb</code> so that it will use the default password-based authentication method if a password parameter is present. Mine looks like the following:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># frozen_string_literal: true</span> <span class="k">module</span> <span class="nn">Devise</span> <span class="k">module</span> <span class="nn">Passwordless</span> <span class="k">class</span> <span class="nc">SessionsController</span> <span class="o">&lt;</span> <span class="no">Devise</span><span class="o">::</span><span class="no">SessionsController</span> <span class="k">def</span> <span class="nf">create</span> <span class="k">super</span> <span class="n">and</span> <span class="k">return</span> <span class="k">if</span> <span class="n">create_params</span><span class="p">[</span><span class="ss">:password</span><span class="p">].</span><span class="nf">present?</span> <span class="nb">self</span><span class="p">.</span><span class="nf">resource</span> <span class="o">=</span> <span class="n">resource_class</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="ss">email: </span><span class="n">create_params</span><span class="p">[</span><span class="ss">:email</span><span class="p">])</span> <span class="nb">self</span><span class="p">.</span><span class="nf">resource</span> <span class="o">||=</span> <span class="n">resource_class</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="ss">username: </span><span class="n">create_params</span><span class="p">[</span><span class="ss">:email</span><span class="p">])</span> <span class="k">if</span> <span class="nb">self</span><span class="p">.</span><span class="nf">resource</span> <span class="n">resource</span><span class="p">.</span><span class="nf">send_magic_link</span><span class="p">(</span><span class="n">create_params</span><span class="p">[</span><span class="ss">:remember_me</span><span class="p">])</span> <span class="n">set_flash_message</span><span class="p">(</span><span class="ss">:notice</span><span class="p">,</span> <span class="ss">:magic_link_sent</span><span class="p">,</span> <span class="ss">now: </span><span class="kp">true</span><span class="p">)</span> <span class="k">else</span> <span class="n">set_flash_message</span><span class="p">(</span><span class="ss">:alert</span><span class="p">,</span> <span class="ss">:not_found_in_database</span><span class="p">,</span> <span class="ss">now: </span><span class="kp">true</span><span class="p">)</span> <span class="k">end</span> <span class="nb">self</span><span class="p">.</span><span class="nf">resource</span> <span class="o">=</span> <span class="n">resource_class</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">create_params</span><span class="p">)</span> <span class="n">render</span> <span class="ss">:new</span> <span class="k">end</span> <span class="kp">protected</span> <span class="k">def</span> <span class="nf">translation_scope</span> <span class="k">if</span> <span class="n">action_name</span> <span class="o">==</span> <span class="s1">'create'</span> <span class="s1">'devise.passwordless'</span> <span class="k">else</span> <span class="k">super</span> <span class="k">end</span> <span class="k">end</span> <span class="kp">private</span> <span class="k">def</span> <span class="nf">create_params</span> <span class="n">resource_params</span><span class="p">.</span><span class="nf">permit</span><span class="p">(</span><span class="ss">:email</span><span class="p">,</span> <span class="ss">:password</span><span class="p">,</span> <span class="ss">:remember_me</span><span class="p">)</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> <span class="k">end</span> </code></pre></div></div> <p>The main things I’ve modified here are permitting the <code class="language-plaintext highlighter-rouge">:password</code> param in <code class="language-plaintext highlighter-rouge">create_params</code>, and calling out to <code class="language-plaintext highlighter-rouge">super</code> if it’s present. I also allow logging in by username or email address, so there’s some extra code to do that as well.</p> <p>You’ll also need to allow users to modify their account without a password. I have this in <code class="language-plaintext highlighter-rouge">app/controllers/devise/users/registrations_controller.rb</code> to allow this for all users:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kp">protected</span> <span class="c1"># Allow updating Devise resources without the current password</span> <span class="k">def</span> <span class="nf">update_resource</span><span class="p">(</span><span class="n">resource</span><span class="p">,</span> <span class="n">params</span><span class="p">)</span> <span class="k">if</span> <span class="n">params</span><span class="p">[</span><span class="ss">:password</span><span class="p">].</span><span class="nf">blank?</span> <span class="n">params</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="ss">:password</span><span class="p">)</span> <span class="n">params</span><span class="p">.</span><span class="nf">delete</span><span class="p">(</span><span class="ss">:password_confirmation</span><span class="p">)</span> <span class="k">if</span> <span class="n">params</span><span class="p">[</span><span class="ss">:password_confirmation</span><span class="p">].</span><span class="nf">blank?</span> <span class="k">end</span> <span class="n">resource</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">params</span><span class="p">)</span> <span class="k">end</span> </code></pre></div></div> <p>Since we’re using <code class="language-plaintext highlighter-rouge">devise-passwordless</code> for our sessions controller, we also need to add a few extra keys to <code class="language-plaintext highlighter-rouge">config/locales/devise.en.yml</code>, e.g.:</p> <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">en</span><span class="pi">:</span> <span class="na">devise</span><span class="pi">:</span> <span class="na">passwordless</span><span class="pi">:</span> <span class="na">user</span><span class="pi">:</span> <span class="na">signed_in</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Signed</span><span class="nv"> </span><span class="s">in</span><span class="nv"> </span><span class="s">successfully."</span> <span class="na">signed_out</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Signed</span><span class="nv"> </span><span class="s">out</span><span class="nv"> </span><span class="s">successfully."</span> <span class="na">already_signed_out</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Signed</span><span class="nv"> </span><span class="s">out</span><span class="nv"> </span><span class="s">successfully."</span> </code></pre></div></div> <p>You may also want to customize some user flows, account settings, or email views depending on if a user has a password set or not. You can easily check this with e.g. <code class="language-plaintext highlighter-rouge">current_user.encrypted_password.blank?</code>.</p> Sun, 13 Mar 2022 00:00:00 +0000 https://blog.podqueue.fm/2022/03/13/flexible_passwordless_rails_authentication_with_devise-passwordless/ https://blog.podqueue.fm/2022/03/13/flexible_passwordless_rails_authentication_with_devise-passwordless/ Using a Rails App Layout with Jekyll <p>There’s a number of different strategies you can use to host a blog for your app or company, and for <a href="https://podqueue.fm">PodQueue</a> I went with the tried-and-true “<a href="https://jekyllrb.com/">static Jekyll site</a> on a blog subdomain” approach. There are tradeoffs to every approach, and one here is that I initially ran the blog with a generic Jekyll theme just to have something up and running. I wanted the blog to instead have the same styling and layout as the main site with just a few blog-specific tweaks, so here’s how I accomplished that.</p> <p>The main PodQueue website runs on Rails, and has an app-wide layout used for most pages. So to generate my Jekyll layout, I have a single page/endpoint in the Rails app that uses that layout with Jekyll’s <code>&lbrace;&lbrace; content &rbrace;&rbrace;</code> templating tag as the only content. I can then just download that <code class="language-plaintext highlighter-rouge">/layout</code> endpoint as the HTML I’m going to use for my Jekyll layout (<code class="language-plaintext highlighter-rouge">_layouts/default.html</code>).</p> <p>As I mentioned, there are a few blog-specific tweaks I want to add to the Rails-generated layout before using it as my Jekyll layout (e.g. YAML front matter, changing “PodQueue” to “PodQueue Blog”, using Jekyll’s <code>&lbrace;&lbrace; page.title &rbrace;&rbrace;</code>, etc.). I made these changes <em>once</em>, then generated a patch file with:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diff -u layout default.html &gt; layout.patch </code></pre></div></div> <p>Now, when I want to update the Jekyll layout from the Rails layout, I can simply re-download the layout, apply the patch, and overwrite my <code class="language-plaintext highlighter-rouge">default.html</code>.</p> <p>Just for good measure, <a href="https://github.com/podqueue/blog.podqueue.fm/blob/main/script/layout.sh">I’ve wrapped all these steps into a script</a> that will automatically update my Jekyll layout from my Rails layout (as well as refresh the patch so that drift over time doesn’t result in patch rejection). Eventually, I can automate this to trigger whenever a successful deploy happens, or just on a periodic basis. You may especially want to do this if you’re referring to production assets (JS/CSS/etc.) which will change with each deploy and become unavailable (I’m using a CDN configuration that allows old assets to stick around, so it’s not quite as crucial). You’ll also want to check that any relative URLs in your layout are correct, and change them to absolute URLs if necessary in your initial patch configuration.</p> Sat, 12 Mar 2022 00:00:00 +0000 https://blog.podqueue.fm/2022/03/12/using_a_rails_app_layout_with_jekyll/ https://blog.podqueue.fm/2022/03/12/using_a_rails_app_layout_with_jekyll/ Announcing PodQueue Gift Certificates <p>Just in time for the holidays, we’ve added support for Gift Certificates to PodQueue! You can find out more about our gift certificates here: <a href="https://podqueue.fm/pages/gift_certificates">https://podqueue.fm/pages/gift_certificates</a></p> <p>For the initial version, we’re only selling $50 gift certificates (equivalent to a one year subscription at the annual rate). Certificates can also be applied to an existing PodQueue account and used for any future invoices!</p> Sat, 04 Dec 2021 00:00:00 +0000 https://blog.podqueue.fm/2021/12/04/announcing_podqueue_gift_certificates/ https://blog.podqueue.fm/2021/12/04/announcing_podqueue_gift_certificates/ PodQueue Referral Program - Give a Month, Get $5 Sat, 09 Oct 2021 00:00:00 +0000 https://blog.podqueue.fm/2021/10/09/podqueue_referral_program/ https://blog.podqueue.fm/2021/10/09/podqueue_referral_program/ PodQueue Now Has Search Support! <p>People are saving enough links in <a href="https://podqueue.fm">PodQueue</a> that they might need some help finding something they’ve saved or listened to earlier, so we’ve added a “Search” feature that will search through episodes and links you’ve saved in your PodQueue account!</p> Sun, 26 Sep 2021 00:00:00 +0000 https://blog.podqueue.fm/2021/09/26/podqueue_now_has_search_support/ https://blog.podqueue.fm/2021/09/26/podqueue_now_has_search_support/ Giving Back to Rails with rails-hidden_autocomplete <p>In the course of developing <a href="https://podqueue.fm">PodQueue</a>, we stumbled across <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=520561">a longstanding bug in Firefox</a> that will populate hidden form inputs with random values if they don’t have the attribute <code class="language-plaintext highlighter-rouge">autocomplete="off"</code>.</p> <p>Since PodQueue is built on <a href="http://rubyonrails.org/">Rails</a>, and Rails uses hidden form inputs extensively for things like CSRF protection, we wound up needing to modify Rails methods that emit hidden form inputs to add this attribute, so that Firefox users wouldn’t see unexpected behavior.</p> <p>To give back to the Rails community, we’ve now packaged this into a standalone Ruby gem that any Rails 6.1 application can easily use: <a href="https://github.com/podqueue/rails-hidden_autocomplete"><code class="language-plaintext highlighter-rouge">rails-hidden_autocomplete</code></a></p> <p>When installed in a Rails app, this gem modifies all the built-in locations that Rails emits a hidden form input to add the necessary <code class="language-plaintext highlighter-rouge">autocomplete="off"</code> attribute, preventing Firefox from accidentally overwriting the hidden input values.</p> Sun, 19 Sep 2021 00:00:00 +0000 https://blog.podqueue.fm/2021/09/19/giving_back_to_rails_with_rails-hidden_autocomplete/ https://blog.podqueue.fm/2021/09/19/giving_back_to_rails_with_rails-hidden_autocomplete/ Generating Static Error Pages with Rails <p>For <a href="https://podqueue.fm">PodQueue</a>, I wanted to generate static error pages that used the same Rails layout and branding as the main site, and while there are many approaches floating around for this, none quite worked exactly the way I wanted them to (or at all) with Rails 6 and Heroku.</p> <p>The most promising approach I found was <a href="https://blog.grio.com/2013/07/generating-static-pages-with-rails.html">this one by Ryan Schultz</a>, however, it was written in 2013 and I had issues with getting the <code class="language-plaintext highlighter-rouge">ActionDispatch::Integration::Session</code>-based page rendering it uses to work. It still forms the basis for my solution though, which is to add a new <code class="language-plaintext highlighter-rouge">rake app:static</code> task to generate the static pages.</p> <p>So in my Rails app’s <code class="language-plaintext highlighter-rouge">lib/tasks/app.rake</code> I have:</p> <script src="https://gist.github.com/ryanfb/c9a2f865583752ae63709cb6152b3807.js"></script> <p>You’ll notice that a major difference here is that I’m using <code class="language-plaintext highlighter-rouge">ApplicationController::Renderer</code> to render the pages into static files, since this seems to be the preferred way of doing it now (specifying the hostname and HTTPS, just in case). There’s also some extra work I’m doing at the end for the <a href="https://devcenter.heroku.com/articles/error-pages">Heroku-specific error pages</a> <code class="language-plaintext highlighter-rouge">application-error.html</code> &amp; <code class="language-plaintext highlighter-rouge">maintenance-mode.html</code>, which get pushed to an S3 bucket. Since the Heroku error pages also get served via an <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code>, I use Nokogiri to rewrite relative asset paths into absolute URLs. (If you’re not using Heroku, you can ignore all that.)</p> <p>The “regular” error pages just get written out to the <code class="language-plaintext highlighter-rouge">public</code> directory and served normally. The <code class="language-plaintext highlighter-rouge">app:static</code> task is hooked onto asset precompilation so that it happens afterwards every time I run a deploy - this is done by using <code class="language-plaintext highlighter-rouge">enhance</code> in my main <code class="language-plaintext highlighter-rouge">Rakefile</code> like so:</p> <div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="p">[</span><span class="s1">'assets:precompile'</span><span class="p">].</span><span class="nf">enhance</span> <span class="k">do</span> <span class="no">Rake</span><span class="o">::</span><span class="no">Task</span><span class="p">[</span><span class="s1">'app:static'</span><span class="p">].</span><span class="nf">invoke</span> <span class="k">end</span> </code></pre></div></div> <p>You may have also noticed that I said I wanted to use the main application layout for my errors, but I’m using <code class="language-plaintext highlighter-rouge">layout/errors</code> in the task. This is because I also <em>do</em> need some error-page-specific logic while still inheriting from the main layout (in <code class="language-plaintext highlighter-rouge">app/views/layouts/errors.html.erb</code>):</p> <script src="https://gist.github.com/ryanfb/949b4fd8e27f095bd46dc37cb7237bb2.js"></script> <p>Setting <code class="language-plaintext highlighter-rouge">content_for :error_page</code> lets us tell from any other layout or view if we’re inside the static error page rendering context, and the <code class="language-plaintext highlighter-rouge">:stylesheets</code> content is used in the main layout to have error-page specific CSS.</p> <p>One important thing for the way I use this is that I also use <a href="https://github.com/heartcombo/devise">Devise</a> for authentication and <code class="language-plaintext highlighter-rouge">ApplicationController.renderer</code> doesn’t have access to Devise’s <code class="language-plaintext highlighter-rouge">Warden::Proxy</code> instance by default, so if you try to use any Devise methods like <code class="language-plaintext highlighter-rouge">user_signed_in?</code> or <code class="language-plaintext highlighter-rouge">current_user</code>, you’ll get the exception:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Devise could not find the `Warden::Proxy` instance on your request environment </code></pre></div></div> <p>So for any layout/view path triggered by our static error pages, we need to guard any Devise calls with a check on <code class="language-plaintext highlighter-rouge">content_for?(:error_page)</code>, e.g. <code class="language-plaintext highlighter-rouge">&lt;% if (!content_for?(:error_page)) &amp;&amp; user_signed_in? %&gt;</code>.</p> <p>I’m also using the <a href="https://github.com/thoughtbot/high_voltage"><code class="language-plaintext highlighter-rouge">high_voltage</code> gem</a> to handle my error pages, but as long as you have a route for your error pages you should still be able to use this technique. Since the error pages use compiled asset slugs that will change constantly, I don’t keep the generated pages in version control.</p> <p>Thanks for reading, and I hope this approach helps you navigate the confusing landscape of Rails static error pages!</p> Sun, 19 Sep 2021 00:00:00 +0000 https://blog.podqueue.fm/2021/09/19/generating_static_error_pages_with_rails/ https://blog.podqueue.fm/2021/09/19/generating_static_error_pages_with_rails/ rails Removing "?ref=producthunt" from your search results <p>After launching <a href="https://podqueue.fm">PodQueue</a>, I submitted it to many of the usual product launch sites, including <a href="https://www.producthunt.com/posts/podqueue">Product Hunt</a>. In submitting PodQueue, I set the webpage to be “<code class="language-plaintext highlighter-rouge">https://podqueue.fm</code>”, which Product Hunt turns into the outbound tracking link “<code class="language-plaintext highlighter-rouge">https://www.producthunt.com/r/f179c9b85faf16</code>”, which in turn resolves to “<code class="language-plaintext highlighter-rouge">https://podqueue.fm/?ref=producthunt</code>”. Fair enough, I could do some click/conversion tracking from that if I wanted to, although I didn’t ask for it and there’s no way to turn it off.</p> <p>Unfortunately, though, what started happening after this is that for various search engines, I now had two search results for the same page competing with each other, “<code class="language-plaintext highlighter-rouge">https://podqueue.fm</code>” (which I wanted people to see and click on) and “<code class="language-plaintext highlighter-rouge">https://podqueue.fm/?ref=producthunt</code>” (it’s the same page but with a not-even-correct referrer now!).</p> <p>I took a double-barrel approach to solving this, and while you may be able to fix it with just one or the other, I fixed it with both, and no longer see duplicated entries for my landing page in search results.</p> <p>The first was to use an <a href="https://en.wikipedia.org/wiki/HTTP_301">HTTP 301 Moved Permanently redirect</a> whenever a <code class="language-plaintext highlighter-rouge">ref</code> parameter is passed. Since PodQueue runs on Rails, I could do this with a line in the relevant controller action:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redirect_to root_url, status: :moved_permanently and return if params['ref'].present? </code></pre></div></div> <p>Now, requesting “<code class="language-plaintext highlighter-rouge">https://podqueue.fm/?ref=producthunt</code>” will tell clients that they should <em>permanently redirect</em> to “<code class="language-plaintext highlighter-rouge">https://podqueue.fm</code>” instead.</p> <p>The second technique was to introduce a metadata tag inside the <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> of the HTML returned at the root URL:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;link rel="canonical" href="https://podqueue.fm"&gt; </code></pre></div></div> <p>This tells clients that parse the HTML that the <em>canonical representation</em> of this link is “<code class="language-plaintext highlighter-rouge">https://podqueue.fm</code>”.</p> <p>Note that you shouldn’t hardcode this in your layout, since then every page will say that the canonical URL is the root URL - you should set it dynamically or conditionally depending on what you want to accomplish.</p> <p>With both of these techniques deployed and in production, search engines now show just my preferred URL for my site instead of two duplicate entries!</p> Fri, 17 Sep 2021 00:00:00 +0000 https://blog.podqueue.fm/2021/09/17/removing_ref_producthunt_from_your_search_results/ https://blog.podqueue.fm/2021/09/17/removing_ref_producthunt_from_your_search_results/ rails