over 8 years ago

Background

  • Using icon fonts, uploaded to S3 and CloudFront points to S3
  • Fonts are precompiled and deployed to S3 with asset_sync
  • One domain per site (staging and production)

Problem

  • Need to use multiple domains (blah.hk now, need to add blah.sg)
  • Fonts are not shown when using .sg
  • S3 is configured correctly (set response header Access-Control-Accept-Origin to whatever the request header Origin is set)
  • CloudFront is caching the files 24 hours by default, which makes sense for most assets

Solution

CloudFront Part

Rails Part

  • Use rack-cors gem
  • Set something like this in application.rb:
config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
  allow do
    origins '*'
    resource '*', {
      headers: :any,
      max_age: 30.days.to_i,
      methods: [:get], # post and options are not needed yet

      credentials: false, # To allow '*' on allow origin

    }
  end
end

This works for HTTP but not HTTPS, since * is not allowed in Access-Control-Accept-Origin for HTTPS

You would need to set like this for HTTPS:

config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
  allow do
    origins '*'
    resource '*', {
      headers: :any,
      max_age: 30.days.to_i,
      methods: [:get], # post and options are not needed yet

      credentials: true, # This return whatever header `Origin` is set in request

    }
  end
end

I only changed credentials to true, but this makes the gem to return what is set in header Origin instead of *

But to deal with CloudFront caching, set this in production.rb

config.static_cache_control = "private, max-age=#{30.days.to_i}"

No caching in CloudFront, but allow caching in user agent (browser)

Extra benefit

If you use Rack::Deflater on your app server (for compressing responses), your font files are also compressed (*.svg mostly)

# config/application.rb

config.middleware.insert_before(ActionDispatch::Static, Rack::Deflater)

Conclusion

  • If your app server is crushed by font requests, tell meeeeee
  • Cloudfront should support header Vary: Origin (Discussion 1, Discussion 2)
  • I really lack knowledge about CORS stuff
  • So much to learn in web development, but I love it ^_^
  • Put comment below if you found anything wrong ~_~
 
almost 9 years ago

Continue from Part 1...

So now you have some default meta tags, but I am sure you have many pages and want unique meta tags on them, right?
So now we need to add more things to allow that.

Step 4 - Add more helper methods

The most commonly changed meta tags are title, description and keywords (though keywords are not quite useful these days)

# module MetaHelper


  # Get title with key, separated by comma

  #

  # @param [String] key The key under meta namespace

  # @param [Hash] variables The variables for interpolation

  #

  # @return [String] Single string with title

  def meta_title(key = DEFAULT_KEY, variables: {})
    translation_key = "meta.#{key}.title"
    I18n.translate!(translation_key, variables)
  rescue I18n::MissingTranslationData
    I18n.translate!("meta.#{DEFAULT_KEY}.title")
  end

  # Get description with key, separated by comma

  #

  # @param [String] key The key under meta namespace

  # @param [Hash] variables The variables for interpolation

  #

  # @return [String] Single string with description

  def meta_description(key = DEFAULT_KEY, variables: {})
    translation_key = "meta.#{key}.description"
    I18n.translate!(translation_key, variables)
  rescue I18n::MissingTranslationData
    I18n.translate!("meta.#{DEFAULT_KEY}.description")
  end

  # Get keywords with key, separated by comma

  #

  # @param [String] key The key under meta namespace

  # @param [Hash] variables The variables for interpolation

  # @param [Boolean] append_keywords Whether to append keywords found to default keywords instead of replace

  #

  # @return [String] Single string with keywords separated by commas

  def meta_keywords(key = DEFAULT_KEY, variables: {}, append_keywords: true)
    translation_key = "meta.#{key}.keywords"
    keywords = []
    keywords << I18n.translate!(translation_key, variables)

    if key != DEFAULT_KEY && append_keywords
      # Append default keywords if we are got custom key and append_keywords set to true

      keywords << I18n.translate!("meta.#{DEFAULT_KEY}.keywords")
    end
    keywords.join(', ')
  rescue I18n::MissingTranslationData
    I18n.translate!("meta.#{DEFAULT_KEY}.keywords")
  end

Then you can change the helper method in part 1 to:

  def display_default_meta_tags
    default_meta_tags = {
      site:         I18n.t('meta.site'),
      separator:    I18n.t('meta.separator').html_safe,
      title:        meta_title,
      description:  meta_description,
      keywords:     meta_keywords,

      og: {
        type:         'website',
        site_name:    I18n.t('meta.site'),
        title:        meta_title,
        description:  meta_description,
      },
    }

    display_meta_tags(default_meta_tags)
  end

Of course it's pointless without custom meta tags:

en:

  meta:

    default: # again this key can be changed

      title: Default title, excluding the site name

      description: Default description

      keywords: avoid, spammy, keywords

    home:

      index:

        title: Welcome!

        description: Awesome description for landing page only

        keywords: extra, keywords

But how do we use it in view template?
We should not call set_meta_tags with a big hash of meta tags
(title, description, keywords, OG tags...)
So let's add the last (really) helper method:

# module MetaHelper

  # Set meta tags `title`, `description`, `keywords` all at once

  # But you should call `display_default_meta_tags` yourself

  #

  # @param [String] key The key under meta namespace, no default key

  # @param [Hash] variables The variables for interpolation

  # @param [Boolean] reverse option passed only to `set_meta_tags`

  # @param [Boolean] append_keywords option passed only to `meta_keywords`

  def set_meta_tags_with_key(key, variables: {}, reverse: true, append_keywords: true)

    set_meta_tags({
      title:        meta_title(key, variables: variables),
      description:  meta_description(key, variables: variables),
      keywords:     meta_keywords(key, variables: variables, append_keywords: append_keywords),

      og: {
        title:        meta_title(key, variables: variables),
        description:  meta_description(key, variables: variables),
      },

      reverse: reverse,
    })
  end

Step 5 - Use it!

# view/home/index.html.haml
- set_meta_tags_with_key('home.index')

One line only!

Step 6 - Improve further

I think this is good enough for the moment
But if you got better idea please share :)

 
almost 9 years ago

My thoughts about putting meta tags on each page for Ruby on Rails.
Without hardcoding strings in templates!

Step 1 - Use MetaTags gem

Use this gem:
https://github.com/kpumuk/meta-tags

Step 2 - Setup default meta tags

Create a helper method

module MetaHelper

    # This key can be changed

  DEFAULT_KEY = 'default'

  def display_default_meta_tags
    default_meta_tags = {
      site:         I18n.t('meta.site'),
      separator:    I18n.t('meta.separator').html_safe,
      title:        I18n.t("meta.#{DEFAULT_KEY}.title"),
      description:  I18n.t("meta.#{DEFAULT_KEY}.description"),
      keywords:     I18n.t("meta.#{DEFAULT_KEY}.keywords"),

            # I think you want it

      og: {
        type:         'website', # I hardcode this since I don't need other types

        site_name:    I18n.t('meta.site'),
        title:        I18n.t("meta.#{DEFAULT_KEY}.title"),
        description:  I18n.t("meta.#{DEFAULT_KEY}.description"),
      },
    }

    display_meta_tags(default_meta_tags) # `display_meta_tags` is from the gem

  end
end

Create a locale file for it

# config/locales/meta.en.yaml

en:

  meta:

    default: # again this key can be changed

      title: Default title, excluding the site name

      description: Default description

      keywords: avoid, spammy, keywords

Use it

# Somewhere in <head> of layout
= display_default_meta_tags

Step 3 - Read part 2!