최후의 수단으로 쓰는 무료 페이지 속도 최적화 스크립트
이 최후의 수단으로 고칠 수 없는 페이지의 속도를 높이세요. 가장 느린 페이지도 개선할 수 있습니다.
무료 최후의 수단 페이지 속도 최적화 스크립트
Core Web Vitals 컨설턴트로서 가끔 고칠 수 없는 페이지를 만난다. 해결 방법을 몰라서 못 고치는 것이 아니다! 곧 교체될 사이트의 많은 부분을 다시 작성해야 하기 때문에 고칠 수 없는 것이다. 또는 WIX, HubSpot, WebFlow 등과 같은 폐쇄적인 CMS 시스템에서 볼 수 있듯이 사이트 코드를 개선할 접근 권한과 제어력이 충분하지 않을 때도 있다. 마지막으로 단순히 예산이 부족할 때다. 흔한 일은 아니지만 클라이언트가 필요한 작업량과 비용을 정확히 예측하지 못하는 경우가 있다.
2026년 3월 Arjen Karel 마지막 검토
"최후의 수단 페이지 속도 최적화 스크립트" 소개
이런 곤경에 처했을 때, 이 스크립트는 더 나은 것을 구축할 때까지 페이지 속도를 조금이라도 개선하기 위한 절박한 최후의 시도다. 이 스크립트는 Mutation Observer를 사용하여 교묘하게 작동한다. 브라우저가 사이트의 Document Object Model을 생성할 때 스크립트가 이를 감시하고, 즉시 개입하여 느린 코드를 더 빠른 코드로 교체한다.
주요 기능:
- 모든 render blocking 스크립트를 가로채서 스크립트 유형을 type="module"로 변경하여 지연시킨다. 이 트릭은 모든 모듈식 스크립트가 기본적으로 지연된다는 점을 활용한다. 인라인 스크립트도 마찬가지다. 이 방식은 모든 페이지 스크립트를 지연시키는 가장 안전한 방법이다.
- 이미지 lazy loading: 모든 이미지에 loading="lazy" 및 decoding="async"가 추가된다. 이는 비동기 이미지 레이아웃 업데이트와 함께 이미지가 보이는 viewport에 거의 들어올 때까지 이러한 이미지 로드를 지연시킨다. 주의: LCP 이미지는 lazy loading하지 않아야 한다. prioImgs 설정을 사용하여 이를 제외하라. LCP 이미지를 lazy loading하면 성능이 저하되는 이유를 확인하라.
- iframe lazy loading. 이미지와 마찬가지로 iframe을 lazy loading하면 더 중요한 자체 콘텐츠의 우선순위를 높일 수 있다!
설정
스크립트는 단일 "config object"를 받는다. 이 설정을 사용하여 중요한 이미지와 스크립트의 지연 또는 lazy loading을 건너뛴다. iframe의 경우는 반대로 작동한다. 설정과 일치하는 iframe만 lazy loading한다. 모든 설정은 정규 표현식으로 이루어진다. 무섭게 들릴 수 있지만 실제로는 매우 간단하다.
- prioScripts: src가 설정과 일치하는 스크립트의 지연을 건너뛴다.
예시: 'jquery|menu'는 jquery와 메뉴 스크립트에 일치한다 - prioImgs: 이미지 이름, 이미지 클래스 또는 이미지 id가 일치하는 모든 이미지에 대해 lazy loading을 건너뛴다.
예시: 'hero'는 <img id="hero" ..>와 <img src="hero.jpg"> 모두 일치한다 - lazyFrames: iframe src가 설정과 일치하는 iframe만 lazy loading한다.
예시: 'youtube|maps'는 모든 YouTube 및 Google Maps iframe을 lazy loading한다.
사용법
한계
앞서 언급했듯이 이 스크립트를 페이지 속도를 고치는 주요 솔루션으로 사용해서는 안 된다. 다른 모든 방법이 실패하고 새 사이트를 적극적으로 작업하는 동안에만 이러한 솔루션이 허용된다!
더 기술적인 관점에서, 이 스크립트는 브라우저(및 preload scanner)와 경쟁한다. 따라서 스크립트가 활성화되기 전에 어떤 느린 요소가 이미 다운로드 트리거되었는지 알 수 없다.
최후의 수단 페이지 속도 최적화 스크립트
다음은 프로덕션 환경에서 사용해야 할 축소 버전이다
!function(t){['prioScripts', 'prioImgs', 'lazyFrames'].forEach(e=>{t[e]=t[e]?RegExp(t[e],"i"):null});let e=new MutationObserver(e=>{e.forEach(({addedNodes:e})=>{e.forEach(e=>{if(1===e.nodeType)switch(e.tagName){case"SCRIPT":if(!t.prioScripts||!t.prioScripts.test(e.src)){let t=e.getAttribute("type");t&&"text/javascript"!==t||e.setAttribute("type","module")}break;case"IMG":console.log(e.outerHTML),t.prioImgs&&(t.prioImgs.test(e.outerHTML)||e.getAttribute("loading"))||(e.setAttribute("loading","lazy"),e.setAttribute("decoding","async"));break;case"IFRAME":t.lazyFrames.test(e.src)&&e.setAttribute("loading","lazy")}})})});/MSIE|Trident/.test(navigator.userAgent)||(e.observe(document.documentElement,{childList:!0,subtree:!0}),document.addEventListener("DOMContentLoaded",()=>{e.disconnect()}))}({prioScripts:"jquery",prioImgs:"hero",lazyFrames:"youtube|maps"}); 다음은 가독성을 높인 스크립트 버전이다. 프로덕션 환경에서 사용하지 마라! 축소 버전을 사용하라!
!function (cfg) {\r\n\r\n // Regexify config or nullify\r\n ['prioScripts', 'prioImgs', 'lazyFrames'].forEach((e) => {\r\n cfg[e] = cfg[e] ? new RegExp(cfg[e], "i") : null;\r\n });\r\n\r\n t0 = performance.now();\r\n\r\n /* Watch mutated nodes */\r\n const mutator = new MutationObserver((e) => {\r\n e.forEach(({ addedNodes: e }) => {\r\n e.forEach((e) => {\r\n switch (e.nodeType) {\r\n case 1:\r\n switch (e.tagName) {\r\n // defer scripts by adding type="module", excusive test on src\r\n case "SCRIPT":\r\n if (!cfg.prioScripts || !cfg.prioScripts.test(e.src)) {\r\n let type = e.getAttribute("type");\r\n if (!type || type === "text/javascript") {\r\n e.setAttribute("type", "module");\r\n }\r\n }\r\n break;\r\n\r\n // lazy load images, excusive test on outerHTML for classname, id etc etc\r\n case "IMG":\r\n console.log(e.outerHTML);\r\n if (!cfg.prioImgs || (!cfg.prioImgs.test(e.outerHTML) && !e.getAttribute("loading"))) {\r\n e.setAttribute("loading", "lazy");\r\n e.setAttribute("decoding", "async");\r\n }\r\n break;\r\n\r\n // lazy load iframes, inclusive test on src\r\n case "IFRAME":\r\n if (cfg.lazyFrames.test(e.src)) {\r\n e.setAttribute("loading", "lazy");\r\n }\r\n break;\r\n }\r\n break;\r\n }\r\n });\r\n });\r\n });\r\n\r\n\r\n // Check for IE\r\n if (!/MSIE|Trident/.test(navigator.userAgent)) {\r\n mutator.observe(document.documentElement, { childList: true, subtree: true });\r\n document.addEventListener("DOMContentLoaded", () => {\r\n mutator.disconnect();\r\n console.log("I quit after watching for " + (performance.now() - t0) + " ms");\r\n });\r\n }\r\n}({\r\n prioScripts: 'jquery',\r\n prioImgs: 'hero',\r\n lazyFrames: 'youtube|maps',\r\n});\r\n\r\n\r\n