diff --git a/.eslintrc.yaml b/.eslintrc.yaml
index a73df2ee3..2cde9d2aa 100644
--- a/.eslintrc.yaml
+++ b/.eslintrc.yaml
@@ -164,7 +164,7 @@ rules:
   jquery/no-parse-html: [2]
   jquery/no-prop: [0]
   jquery/no-proxy: [2]
-  jquery/no-ready: [0]
+  jquery/no-ready: [2]
   jquery/no-serialize: [2]
   jquery/no-show: [2]
   jquery/no-size: [2]
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 80edc0548..839289e9d 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -1,7 +1,6 @@
 // bootstrap module must be the first one to be imported, it handles webpack lazy-loading and global errors
 import './bootstrap.js';
 
-import $ from 'jquery';
 import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue';
 import {initDashboardRepoList} from './components/DashboardRepoList.vue';
 
@@ -90,6 +89,7 @@ import {initCaptcha} from './features/captcha.js';
 import {initRepositoryActionView} from './components/RepoActionView.vue';
 import {initGlobalTooltips} from './modules/tippy.js';
 import {initGiteaFomantic} from './modules/fomantic.js';
+import {onDomReady} from './utils/dom.js';
 
 // Run time-critical code as soon as possible. This is safe to do because this
 // script appears at the end of <body> and rendered HTML is accessible at that point.
@@ -98,7 +98,7 @@ initFormattingReplacements();
 // Init Gitea's Fomantic settings
 initGiteaFomantic();
 
-$(document).ready(() => {
+onDomReady(() => {
   initGlobalCommon();
 
   initGlobalTooltips();
diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js
index d94d4cb09..80c9f01cf 100644
--- a/web_src/js/utils/dom.js
+++ b/web_src/js/utils/dom.js
@@ -67,3 +67,11 @@ export function hideElem(el) {
 export function toggleElem(el, force) {
   elementsCall(el, toggleShown, force);
 }
+
+export function onDomReady(cb) {
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', cb);
+  } else {
+    cb();
+  }
+}