diff --git a/app/models/communication/website.rb b/app/models/communication/website.rb
index 31b134effe1672da24f45207acf0b84bb9b1fd71..15048d7d5e32eeaa48510b9a30b56809409c0fe6 100644
--- a/app/models/communication/website.rb
+++ b/app/models/communication/website.rb
@@ -54,6 +54,9 @@ class Communication::Website < ApplicationRecord
   has_one :imported_website,
           class_name: 'Communication::Website::Imported::Website',
           dependent: :destroy
+  has_many :github_files,
+           class_name: 'Communication::Website::GithubFile',
+           dependent: :destroy
 
   after_create :create_home
   after_save :publish_about_object, if: :saved_change_to_about_id?
diff --git a/app/models/communication/website/github_file.rb b/app/models/communication/website/github_file.rb
index 9f456c6e97d5a5d72e22dc24cd1bfe577f7edf8f..17aa444517ca08198c3f580f910f9a72d2867926 100644
--- a/app/models/communication/website/github_file.rb
+++ b/app/models/communication/website/github_file.rb
@@ -27,17 +27,42 @@ class Communication::Website::GithubFile < ApplicationRecord
 
   def publish
     return unless github.valid?
-    if github.publish(path: about.github_path_generated,
-                      previous_path: github_path,
-                      commit: github_commit_message,
-                      data: about.to_jekyll(self))
+    params = github_params.merge({
+      commit: github_commit_message
+    })
+    if github.publish(params)
       update_column :github_path, about.github_path_generated
+      publish_media
     end
   end
   handle_asynchronously :publish, queue: 'default'
 
+  def publish_media
+    return unless about.respond_to?(:active_storage_blobs)
+    about.active_storage_blobs.each { |blob| publish_blob(blob) }
+  end
+
+  def publish_blob(blob)
+    return unless github.valid?
+    params = github_blob_params(blob).merge({
+      commit: github_blob_commit_message(blob)
+    })
+    github.publish(params)
+  end
+  handle_asynchronously :publish_blob, queue: 'default'
+
   def add_to_batch(github)
+    github.add_to_batch github_params
+    add_media_to_batch(github)
+  end
+
+  def add_media_to_batch(github)
+    return unless about.respond_to?(:active_storage_blobs)
+    about.active_storage_blobs.each { |blob| add_blob_to_batch(github, blob) }
+  end
 
+  def add_blob_to_batch(github, blob)
+    github.add_to_batch github_blob_params(blob)
   end
 
   def github_frontmatter
@@ -60,10 +85,34 @@ class Communication::Website::GithubFile < ApplicationRecord
     @github ||= Github.with_website(website)
   end
 
+  def github_params
+    {
+      path: about.github_path_generated,
+      previous_path: github_path,
+      data: about.to_jekyll(self)
+    }
+  end
+
+  def github_blob_params(blob)
+    blob.analyze unless blob.analyzed?
+    {
+      path: "_data/media/#{blob.id[0..1]}/#{blob.id}.yml",
+      data: ApplicationController.render(
+        template: 'active_storage/blobs/jekyll',
+        layout: false,
+        assigns: { blob: blob }
+      )
+    }
+  end
+
   def github_commit_message
     "[#{about.class.name.demodulize}] Save #{about.to_s}"
   end
 
+  def github_blob_commit_message(blob)
+    "[Medium] Save ##{blob.id}"
+  end
+
   def github_remove_commit_message
     "[#{about.class.name.demodulize}] Remove #{about.to_s}"
   end
diff --git a/app/models/communication/website/imported/website.rb b/app/models/communication/website/imported/website.rb
index 06ccdf35c67358f42ff18d7ebc2cb0b255bf5420..9dabc4851e20c763a92732d8d385bb5fce8a225f 100644
--- a/app/models/communication/website/imported/website.rb
+++ b/app/models/communication/website/imported/website.rb
@@ -60,7 +60,7 @@ class Communication::Website::Imported::Website < ApplicationRecord
 
   def sync_authors
     begin
-      Communication::Website::Author.skip_callback(:save, :after, :publish_to_github)
+      Communication::Website::Author.skip_callback(:save, :after, :publish_github_files)
       wordpress.authors.each do |data|
         author = authors.where(university: university, identifier: data['id']).first_or_initialize
         author.data = data
@@ -69,13 +69,13 @@ class Communication::Website::Imported::Website < ApplicationRecord
       # Batch update all changes (1 query only, good for github API limits)
       website.publish_authors!
     ensure
-      Communication::Website::Author.set_callback(:save, :after, :publish_to_github)
+      Communication::Website::Author.set_callback(:save, :after, :publish_github_files)
     end
   end
 
   def sync_categories
     begin
-      Communication::Website::Category.skip_callback(:save, :after, :publish_to_github)
+      Communication::Website::Category.skip_callback(:save, :after, :publish_github_files)
       wordpress.categories.each do |data|
         category = categories.where(university: university, identifier: data['id']).first_or_initialize
         category.data = data
@@ -85,7 +85,7 @@ class Communication::Website::Imported::Website < ApplicationRecord
       # Batch update all changes (1 query only, good for github API limits)
       website.publish_categories!
     ensure
-      Communication::Website::Category.set_callback(:save, :after, :publish_to_github)
+      Communication::Website::Category.set_callback(:save, :after, :publish_github_files)
     end
   end
 
@@ -99,7 +99,7 @@ class Communication::Website::Imported::Website < ApplicationRecord
 
   def sync_pages
     begin
-      Communication::Website::Page.skip_callback(:save, :after, :publish_to_github)
+      Communication::Website::Page.skip_callback(:save, :after, :publish_github_files)
       wordpress.pages.each do |data|
         page = pages.where(university: university, identifier: data['id']).first_or_initialize
         page.data = data
@@ -109,13 +109,13 @@ class Communication::Website::Imported::Website < ApplicationRecord
       # Batch update all changes (1 query only, good for github API limits)
       website.publish_pages!
     ensure
-      Communication::Website::Page.set_callback(:save, :after, :publish_to_github)
+      Communication::Website::Page.set_callback(:save, :after, :publish_github_files)
     end
   end
 
   def sync_posts
     begin
-      Communication::Website::Post.skip_callback(:save, :after, :publish_to_github)
+      Communication::Website::Post.skip_callback(:save, :after, :publish_github_files)
       wordpress.posts.each do |data|
         post = posts.where(university: university, identifier: data['id']).first_or_initialize
         post.data = data
@@ -124,7 +124,7 @@ class Communication::Website::Imported::Website < ApplicationRecord
       # Batch update all changes (1 query only, good for github API limits)
       website.publish_posts!
     ensure
-      Communication::Website::Post.set_callback(:save, :after, :publish_to_github)
+      Communication::Website::Post.set_callback(:save, :after, :publish_github_files)
     end
   end
 
diff --git a/app/models/communication/website/with_batch_publication.rb b/app/models/communication/website/with_batch_publication.rb
index 9fe423ed0047f417e6b1ae1a8bfbaf2b2a4ee03c..0692613531fa46127f9f0dc07ceaba68ad26326c 100644
--- a/app/models/communication/website/with_batch_publication.rb
+++ b/app/models/communication/website/with_batch_publication.rb
@@ -3,103 +3,59 @@ module Communication::Website::WithBatchPublication
 
   included do
     def force_publish!
-      publish_authors!
-      publish_categories!
-      publish_pages!
-      publish_posts!
-      publish_menus!
-      publish_school! if about.is_a?(Education::School)
-      publish_journal! if about.is_a?(Research::Journal)
+      commit_files_in_batch github_files,
+                            "[Website] Batch update from import"
     end
     handle_asynchronously :force_publish!, queue: 'default'
 
     def publish_authors!
-      publish_objects(Communication::Website::Author, authors)
+      commit_files_in_batch github_files.where(about_type: "Communication::Website::Author"),
+                            "[Author] Batch update from import"
     end
 
     def publish_categories!
-      publish_objects(Communication::Website::Category, categories)
+      commit_files_in_batch github_files.where(about_type: "Communication::Website::Category"),
+                            "[Category] Batch update from import"
     end
 
     def publish_pages!
-      publish_objects_with_blobs(Communication::Website::Page, pages)
+      commit_files_in_batch github_files.where(about_type: "Communication::Website::Page"),
+                            "[Page] Batch update from import"
     end
 
     def publish_posts!
-      publish_objects_with_blobs(Communication::Website::Post, posts)
+      commit_files_in_batch github_files.where(about_type: "Communication::Website::Post"),
+                            "[Post] Batch update from import"
     end
 
     def publish_menus!
-      publish_objects(Communication::Website::Menu, menus)
+      commit_files_in_batch github_files.where(about_type: "Communication::Website::Menu"),
+                            "[Menu] Batch update from import"
     end
 
     def publish_school!
-      github.publish(path: about.github_path, commit: "[School] Save #{about.to_s}", data: about.to_jekyll)
-      publish_shared_objects(Education::Program, about.programs)
-      publish_shared_objects(Education::Teacher, about.teachers)
+      commit_files_in_batch github_files.where(about_type: [
+                              "Education::School",
+                              "Education::Program",
+                              "Education::Teacher"
+                            ]),
+                            "[Education School/Program/Teacher] Batch update from import"
     end
 
     def publish_journal!
-      github.publish(path: about.github_path, commit: "[Journal] Save #{about.to_s}", data: about.to_jekyll)
-      publish_shared_objects(Research::Article, about.articles)
-      publish_shared_objects(Research::Volume, about.volumes)
+      commit_files_in_batch github_files.where(about_type: [
+                              "Research::Journal",
+                              "Research::Journal::Article",
+                              "Research::Journal::Volume"
+                            ]),
+                            "[Research Journal/Article/Volume] Batch update from import"
     end
 
     protected
 
-    def publish_objects(model, objects)
-      return if objects.empty?
-      begin
-        had_callback = model.__callbacks[:save].find { |c| c.matches?(:after, :publish_to_github) }
-        model.skip_callback(:save, :after, :publish_to_github) if had_callback
-        return unless github.valid?
-        objects.each do |object|
-          github.add_to_batch path: object.github_path_generated,
-                              previous_path: object.github_path,
-                              data: object.to_jekyll
-          yield(github, object) if block_given?
-        end
-        github.commit_batch "[#{model.name.demodulize}] Batch update from import"
-      ensure
-        model.set_callback(:save, :after, :publish_to_github) if had_callback
-      end
-    end
-
-    def publish_objects_with_blobs(model, objects)
-      publish_objects(model, objects) { |github, object|
-        object.active_storage_blobs.each do |blob|
-          blob.analyze unless blob.analyzed?
-          github_path = "_data/media/#{blob.id[0..1]}/#{blob.id}.yml"
-          data = ApplicationController.render(
-            template: 'active_storage/blobs/jekyll',
-            layout: false,
-            assigns: { blob: blob }
-          )
-          github.add_to_batch(path: github_path, data: data)
-        end
-      }
-    end
-
-    def publish_shared_objects(model, objects)
-      return if objects.empty?
-      begin
-        had_callback = model.__callbacks[:save].find { |c| c.matches?(:after, :publish_to_every_websites) }
-        model.skip_callback(:save, :after, :publish_to_every_websites) if had_callback
-        return unless github.valid?
-        clean_model_name = model.name.demodulize
-        objects.each do |object|
-          if object.respond_to?(:github_path)
-            github_path = object.github_path
-          else
-            root_folder = "_#{clean_model_name.pluralize.underscore}"
-            github_path = "#{root_folder}/#{object.id}.md"
-          end
-          github.add_to_batch(path: github_path, data: object.to_jekyll)
-        end
-        github.commit_batch "[#{clean_model_name}] Batch update from import"
-      ensure
-        model.set_callback(:save, :after, :publish_to_every_websites) if had_callback
-      end
+    def commit_files_in_batch(files, commit_message)
+      files.find_each { |file| file.add_to_batch(github) }
+      github.commit_batch commit_message
     end
   end
 
diff --git a/app/models/communication/website/with_media.rb b/app/models/communication/website/with_media.rb
index 3b354cdfc4c2c46e53659105b4053d405a5d7198..c4aad2b352486abc95685d5ba03e22f6d97d99d5 100644
--- a/app/models/communication/website/with_media.rb
+++ b/app/models/communication/website/with_media.rb
@@ -1,23 +1,8 @@
 module Communication::Website::WithMedia
   extend ActiveSupport::Concern
 
-  included do
-    after_save_commit :publish_media_to_github
-    after_destroy :remove_media_from_github
-  end
-
   def active_storage_blobs
     blob_ids = [featured_image&.blob_id, text.embeds.blobs.pluck(:id)].flatten.compact
     university.active_storage_blobs.where(id: blob_ids)
   end
-
-  protected
-
-  def publish_media_to_github
-    active_storage_blobs.each { |blob| website.publish_blob(blob) }
-  end
-
-  def remove_media_from_github
-    active_storage_blobs.each { |blob| website.remove_blob(blob) }
-  end
 end