회원가입 | 고객센터 |
DESIGNONEX
dxcms.kr
로그인 회원가입
고객센터
12. 성능 / 최적화

정적 리소스 관리

D DX
2026.05.10 16:12(수정됨) 126 0

1장. 정적 리소스 관리 개요

DXCMS의 정적 리소스(CSS, JS, 이미지, 폰트)는 .htaccess URL 재작성, DX_VERSION 기반 캐시 버스팅, CDN 활용, dxb-css 런타임 엔진, DxThumb 이미지 최적화 다섯 가지 전략으로 관리됩니다. 정적 파일은 PHP를 거치지 않고 웹서버가 직접 서빙하여 응답 시간을 최소화합니다.


1.1 정적 리소스 파일 구조

{사이트 루트}/
  assets/
    css/
      dx-utils.css       — 정적 유틸리티 클래스 (Tailwind-like, 8.6KB)
      dx.css             — 공통 컴포넌트 CSS
      mypage.css         — 마이페이지 전용 CSS
      board/gallery/style.css  — 갤러리 스킨 CSS
      board/shop/style.css     — 쇼핑 스킨 CSS
    js/
      dxb-css.js         — 런타임 Tailwind-like CSS 엔진 v6.0.0 (33KB 빌드 포함)
      dx-draft.js        — 글쓰기 임시저장 JS (33KB)
      dx-session-guard.js — 세션 가드 JS (14KB)
      dx-rescue.js       — 세션 긴급 구조 JS v1.2.0
      dx-darkmode-engine.js — 다크모드 엔진
      dx.js              — 공통 유틸리티 JS
  themes/
    default/
      layout/
        main.php         — 레이아웃 (CSS/JS 로딩, preconnect, CDN)
        style.css        — 테마 전용 CSS (컴포넌트 포함)
  data/
    uploads/             — 업로드 파일 (직접 서빙)
    uploads/thumbnails/  — 썸네일 이미지 (GD 생성)


1.2 리소스 서빙 흐름

브라우저 → GET /assets/css/dx-utils.css?v=8.1.0
  ↓
.htaccess RewriteRule:
  "^(.*/)?assets(/.*)?$ - [L]"  → PHP 우회, 파일 직접 서빙
  ↓
Apache/Nginx가 파일 직접 반환
  → PHP index.php 실행 안 함
  → 응답 시간 < 1ms

브라우저 → GET /free/view/123 (동적 페이지)
  ↓
.htaccess RewriteRule:
  "^ index.php [QSA,L]"  → PHP index.php 실행
  → 라우팅 → 게시글 조회 → HTML 렌더링
  → 응답 시간 50~200ms


2장. .htaccess 정적 리소스 설정

.htaccess는 DXCMS의 모든 URL 처리와 정적 파일 서빙을 제어합니다. Apache 2.2/2.4, Linux/Windows, 루트/서브디렉토리 설치 모두 지원합니다.


2.1 정적 자산 최우선 허용

# .htaccess 핵심 규칙 — 정적 자산 최우선 허용

# assets/, data/uploads/, themes/ 를 PHP 보다 먼저 처리
# CKEditor가 assets/ckeditor4/plugins/ 를 로드하므로 assets 전체 허용 필수
RewriteRule ^(.*/)?assets(/.*)?$ - [L]
RewriteRule ^(.*/)?data/uploads(/.*)?$ - [L]
RewriteRule ^(.*/)?themes(/.*)?$ - [L]

# 실존하는 정적 파일 직접 서빙 (css, js, 이미지 등 — PHP 파일 제외)
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_FILENAME} !.php$
RewriteRule ^ - [L]

# 나머지 전부 → index.php (단일 진입점)
RewriteRule ^ index.php [QSA,L]

💡 [L] 플래그의 의미
"[L]" = Last. 이 규칙이 매칭되면 이후 규칙은 검사하지 않음.
assets/ 로 시작하는 URL → [L] 적용 → index.php로 넘어가지 않음.
→ PHP 실행 없이 파일 직접 서빙 → 응답 속도 극대화.

"- [L]" 에서 "-"는 URL을 변경하지 않고 그대로 사용한다는 의미.
파일 경로를 변경하지 않고 웹서버가 원래 경로로 파일을 찾아 반환.


2.2 민감 폴더 접근 차단

# core/, data/, extend/ 등 민감 폴더 HTTP 직접 접근 차단
# assets/ 는 제외 (위에서 먼저 허용됨)
RewriteRule ^(.*/)?(core|data|extend|controllers|routes|docs)(/.*)?$ - [F,L]

# [F] = Forbidden. HTTP 403 반환
# 예: GET /core/auth/Auth.php → 403
# 예: GET /data/config.php   → 403
# 예: GET /extend/bottom/01_rescue_gc.php → 403


2.3 PHP 직접 실행 차단

# 모든 .php 파일 직접 실행 차단 (index.php 만 허용)
<FilesMatch ".php$">
    <IfModule mod_authz_core.c>   # Apache 2.4
        Require all denied
    </IfModule>
    <IfModule !mod_authz_core.c>  # Apache 2.2
        Order Allow,Deny
        Deny from all
    </IfModule>
</FilesMatch>

<Files "index.php">
    <IfModule mod_authz_core.c>
        Require all granted
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order Allow,Deny
        Allow from all
    </IfModule>
</Files>

# 보안 효과:
# GET /boards/handler.php → 403 (외부에서 직접 실행 불가)
# GET /core/Secure.php    → 403
# GET /index.php          → 허용 (단일 진입점)


2.4 민감 파일 다운로드 차단

# .env, .log, .bak, .sql, .conf, .ini, .sh, .bat 파일 다운로드 차단
<FilesMatch ".(env|log|bak|sql|conf|ini|sh|bat)$">
    Require all denied
</FilesMatch>

# GET /data/error.log     → 403
# GET /data/config.php    → .php 차단 규칙 적용
# GET /.env               → 403


2.5 보안 헤더 (.htaccess 레벨)

<IfModule mod_headers.c>
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always unset X-Powered-By
</IfModule>

# .htaccess와 PHP(Secure.php) 두 곳에서 헤더 설정하는 이유:
# .htaccess: 정적 파일(CSS, JS, 이미지) 응답에도 헤더 추가
# PHP:       동적 페이지 응답에 헤더 추가 + CSP, HSTS 등 추가 헤더

⚠️ .htaccess가 없거나 mod_rewrite 미활성 시
Apache에서 mod_rewrite가 비활성화되면 URL 재작성이 안 되어
모든 요청이 404 또는 파일 직접 실행으로 처리됩니다.

해결 방법 1: a2enmod rewrite && service apache2 restart
해결 방법 2: AllowOverride All 설정 확인 (httpd.conf 또는 apache2.conf)
해결 방법 3: Nginx 사용 시 별도 nginx.conf location 블록 필요 (아래 참고)


2.6 Nginx 환경 동등 설정

# nginx.conf 또는 sites-available/dxcms.conf

server {
    listen 80;
    root /var/www/dxcms;
    index index.php;

    # 정적 자산 직접 서빙 (PHP 우회)
    location ~* ^/(assets|data/uploads|themes)/ {
        try_files $uri $uri/ =404;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 민감 폴더 차단
    location ~* ^/(core|data|extend)/ {
        deny all;
        return 403;
    }

    # PHP 직접 실행 차단 (index.php 제외)
    location ~* .php$ {
        deny all;
    }
    location = /index.php {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # 나머지 → index.php
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
}


3장. DX_VERSION 기반 캐시 버스팅

브라우저는 CSS/JS 파일을 URL 단위로 캐싱합니다. URL이 같으면 새 버전이 배포되어도 브라우저는 이전 캐시를 사용합니다. DX_VERSION을 쿼리 파라미터로 추가하면 버전이 바뀔 때 URL도 바뀌어 새 파일을 강제로 내려받게 됩니다.


3.1 버전 상수 정의

// index.php — 최상단 상수 정의
define('DX_VERSION', '8.1.0');

// 이 상수가 모든 정적 파일 URL의 버전 쿼리 파라미터 기준
// 업데이트 시 이 값만 바꾸면 모든 CSS/JS 캐시가 한 번에 갱신됨


3.2 레이아웃에서 버전 버스팅 적용 (main.php)

<!-- themes/default/layout/main.php -->

<!-- 테마 유틸리티 CSS -->
<link rel="stylesheet"
      href="<?php echo dx_base_url('assets/css/dx-utils.css'); ?>?v=<?php echo DX_VERSION; ?>">
<!-- 출력: /assets/css/dx-utils.css?v=8.1.0 -->

<!-- 테마 레이아웃 CSS -->
<link rel="stylesheet"
      href="<?php echo dx_base_url('themes/default/layout/style.css'); ?>?v=<?php echo DX_VERSION; ?>">
<!-- 출력: /themes/default/layout/style.css?v=8.1.0 -->

<!-- 세션 가드 JS -->
<script src="<?php echo dx_base_url('assets/js/dx-session-guard.js'); ?>?v=<?php echo DX_VERSION; ?>">
</script>
<!-- 출력: /assets/js/dx-session-guard.js?v=8.1.0 -->


3.3 게시판 스킨에서 버전 버스팅

<!-- themes/default/board/write.php -->

<!-- dx-rescue.js — 버전 v1.2.0 명시 (기능 버전) -->
<script src="<?php echo dx_base_url('assets/js/dx-rescue.js'); ?>?v=1.2.0"></script>

<!-- boards/skins/gallery/list.php 등 스킨 CSS -->
<link rel="stylesheet"
      href="<?php echo dx_base_url('assets/css/board/gallery/style.css'); ?>
            ?v=<?php echo DX_VERSION; ?>">


3.4 버전 버스팅 동작 원리

상황 브라우저 동작
첫 방문 (/assets/css/dx-utils.css?v=8.1.0) 파일 다운로드 → 캐시에 저장 (키: URL 전체)
재방문 (버전 동일) URL 동일 → 캐시 HIT → 서버 요청 없음 (0ms)
업데이트 후 (v=8.2.0으로 변경) URL 변경 → 캐시 MISS → 새 파일 다운로드
강제 새로고침 (Ctrl+Shift+R) 브라우저 캐시 무시 → 항상 서버 요청

💡 ?v= 파라미터는 서버에서 무시됨
.htaccess의 정적 파일 규칙은 쿼리 파라미터를 무시합니다.
GET /assets/css/dx-utils.css?v=8.1.0
→ 서버는 /assets/css/dx-utils.css 파일을 그대로 반환
→ ?v=8.1.0 파라미터는 브라우저 캐시 키로만 사용됨
→ 서버에서 버전별 파일을 따로 관리할 필요 없음


3.5 테마/플러그인 개발 시 버전 버스팅 적용

// 방법 1: DX_VERSION 상수 사용 (권장)
<link rel="stylesheet"
      href="<?php echo dx_base_url('plugins/my-plugin/style.css'); ?>
            ?v=<?php echo DX_VERSION; ?>">

// 방법 2: 플러그인 독자 버전 사용
<script src="<?php echo dx_base_url('plugins/my-plugin/main.js'); ?>?v=1.0.3">
</script>

// 방법 3: 파일 수정 시각 사용 (가장 정확, 개발 환경)
$mtime = filemtime(DX_ROOT . '/assets/css/my-style.css');
echo dx_base_url('assets/css/my-style.css') . '?v=' . $mtime;
// 출력: /assets/css/my-style.css?v=1746957742
// 파일을 수정할 때마다 mtime이 바뀌어 자동 캐시 갱신


4장. CDN 리소스 관리

DXCMS 기본 테마는 외부 CDN에서 폰트, 아이콘, 라이브러리를 로드합니다. CDN을 사용하면 대역폭을 절약하고 캐시 공유 효과를 얻지만, CDN 장애 시 리소스 로드에 영향을 받을 수 있습니다.


4.1 사용하는 CDN 목록

리소스 CDN URL
Pretendard 폰트 jsDelivr cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css
Montserrat 폰트 Google Fonts fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900&display=swap
Font Awesome 6.5.2 cdnjs.cloudflare.com cloudflare/ajax/libs/font-awesome/6.5.2/css/all.min.css
jQuery 3.7.1 cdnjs.cloudflare.com cloudflare/ajax/libs/jquery/3.7.1/jquery.min.js
Highlight.js 11.9.0 cdnjs.cloudflare.com cloudflare/ajax/libs/highlight.js/11.9.0/highlight.min.js


4.2 preconnect — DNS 사전 연결

브라우저가 CDN 서버에 연결하려면 DNS 조회 + TCP 연결 + TLS 핸드셰이크 시간이 필요합니다. preconnect로 이 과정을 미리 처리해 첫 번째 리소스 로드 속도를 높입니다.
<!-- themes/default/layout/main.php -->

<!-- jsDelivr (Pretendard 폰트) — crossorigin 필수 (CORS 폰트) -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>

<!-- Google Fonts — 두 도메인 모두 preconnect -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- Font Awesome, jQuery, Highlight.js는 cloudflare CDN 공유 -->
<!-- 브라우저가 이미 다른 사이트에서 연결해둔 경우 재사용 가능 -->

// preconnect 효과 (네트워크 상황에 따라)
// DNS 조회:  50~200ms 절감
// TCP 연결:  20~100ms 절감
// TLS 핸드셰이크: 50~150ms 절감
// 합계: 폰트 첫 로드 시 최대 450ms 절감 가능


4.3 Google Fonts display=swap

<!-- display=swap: 폰트 로딩 중 시스템 폰트로 먼저 표시 -->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700;800;900
      &display=swap" rel="stylesheet">

// display=swap 없으면:
// 폰트 로딩 완료까지 텍스트 숨김 (FOIT: Flash of Invisible Text)
// → 사용자가 텍스트를 못 보는 빈 화면 경험

// display=swap 있으면:
// 시스템 폰트로 즉시 표시 (FOUT: Flash of Unstyled Text)
// → 폰트 로딩 완료 후 웹폰트로 교체
// → 사용자가 내용을 바로 읽을 수 있음


4.4 CDN 리소스 자체 호스팅으로 전환

CDN 의존성을 없애고 싶거나 인터넷이 차단된 환경에서는 리소스를 자체 서버에 올려 사용할 수 있습니다.
// 예: Pretendard 폰트 자체 호스팅
// 1. https://github.com/orioncactus/pretendard 에서 폰트 다운로드
// 2. assets/fonts/pretendard/ 폴더에 저장
// 3. main.php에서 CDN 링크 제거하고 로컬 경로로 변경

// 기존 (CDN)
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/
      pretendard@v1.3.9/dist/web/static/pretendard.min.css">

// 변경 (자체 호스팅)
<link rel="stylesheet"
      href="<?php echo dx_base_url('assets/css/pretendard.min.css'); ?>
            ?v=<?php echo DX_VERSION; ?>">

// jQuery 자체 호스팅 예시
// 1. jquery.min.js 다운로드 → assets/js/ 저장
// 기존: <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js">
// 변경: <script src="<?php echo dx_base_url('assets/js/jquery.min.js'); ?>?v=3.7.1">


5장. dxb-css.js — 런타임 Tailwind-like CSS 엔진

dxb-css v6.0.0 "Singularity Edition"은 Tailwind CSS의 빌드 과정 없이 브라우저 런타임에서 유틸리티 클래스를 생성합니다. 템플릿 파일에서 flex, hidden, lg:block 등의 클래스를 그대로 사용할 수 있습니다.


5.1 동작 원리

// dxb-css v6.0.0 런타임 처리 흐름

1. 페이지 로드 → DOM의 모든 class 속성 스캔

2. Trie 분기로 클래스 분류 (v6.0.0 핵심 최적화)
   기존: 89개 규칙을 매 클래스마다 순회 (O(n))
   개선: 접두사(flex, bg-, text-, p-, m-, ...) 기반 Trie 분기
         → 평균 4~6개 규칙만 검사 (90% 연산 절감)

3. ruleCache Map에 결과 영구 캐싱
   동일 클래스의 CSS 재생성 없음

4. <style id="dxb-css-generated"> 에 CSS 주입
   DocumentFragment 대신 단일 문자열 배치 → 1회 DOM 조작

5. MutationObserver로 동적 DOM 변화 감지
   requestAnimationFrame으로 배치 처리
   → 스크롤 성능 저하 없음


5.2 지원하는 유틸리티 클래스

카테고리 예시 클래스
레이아웃 flex, grid, hidden, block, inline, items-center, justify-between, gap-4
반응형 sm:, md:, lg:, xl: (브레이크포인트 접두사)
여백 p-4, px-8, py-2, m-auto, mx-4, mt-2, space-x-4
크기 w-full, h-screen, max-w-[1200px], min-w-0
텍스트 text-sm, font-bold, text-center, text-[14px], truncate
배경/테두리 bg-white, border, rounded-xl, shadow-lg
다크모드 dark:bg-gray-900, dark:text-white (.dark 클래스 방식)
hover/focus hover:bg-blue-500, focus:outline-none, group-hover:opacity-100
Arbitrary values w-[220px], h-[68px], text-[#1a73e8], max-w-[1200px]
스크롤 scroll-snap-x, scroll-behavior-smooth, overflow-x-auto
접근성 sr-only, not-sr-only, aria-* variant


5.3 dx-utils.css — 정적 유틸리티 (reflow 없음)

dxb-css.js가 런타임에 CSS를 생성하는 동안 레이아웃이 깜빡이는 문제(FOUC: Flash of Unstyled Content)를 방지하기 위해, 자주 사용하는 유틸리티 클래스를 미리 정적 CSS 파일로 제공합니다.
// assets/css/dx-utils.css (8.6KB)
// dxb-css 런타임의 사전 로드 버전
// 페이지 파싱 즉시 적용 → reflow 없음

// 포함된 클래스:
.flex         { display: flex; }
.hidden       { display: none; }
.block        { display: block; }
.grid         { display: grid; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.w-full       { width: 100%; }
.overflow-hidden { overflow: hidden; }
.whitespace-nowrap { white-space: nowrap; }
.truncate     { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* ... 300+ 클래스 */

// dxb-css.js와 dx-utils.css의 역할 분담:
// dx-utils.css: 자주 쓰는 기본 클래스 → 즉시 적용 (no-JS도 동작)
// dxb-css.js:   동적 클래스, arbitrary value, 반응형 → 런타임 생성


5.4 dxb-css.js 로드 방식

// functions.php — dx_head_assets() 함수
// dx_head 훅(priority=1)에서 자동 로드

dx_add_hook("dx_head", function() {
    $jsPath = DX_ROOT . "/assets/js/dxb-css.js";
    if (!file_exists($jsPath)) return;
    $jsUrl = dx_base_url("assets/js/dxb-css.js");
    echo '<script src="' . $jsUrl . '"></script>' . "\n";
}, 1);  // priority=1: 가장 먼저 실행

// 로드 타이밍: <head> 내 다른 훅보다 먼저
// → dxb-css가 DOM 파싱 전에 준비되어 FOUC 최소화
// → 버전 쿼리 파라미터 없음: dxb-css 자체 캐싱(localStorage) 사용


5.5 dxb-css.js localStorage 캐싱

// dxb-css는 생성된 CSS를 localStorage에 캐싱 (v6.0.0)
// 재방문 시 캐시에서 CSS를 즉시 로드 → DOM 스캔 시간 절감

// localStorage 저장
localStorage.setItem("dxb-css-cache", generatedCss);
localStorage.setItem("dxb-css-hash", domClassHash);

// 재방문 시 캐시 히트 조건:
// 현재 페이지의 클래스 해시 === 저장된 해시
// → 캐시에서 즉시 <style> 주입

// 용량 초과 시 자동 분할 저장 또는 포기 (v6.0.0 개선)
// localStorage 5MB 한도 초과 → 분할 저장 시도
// 분할도 불가 → localStorage 사용 포기, 매 로드마다 재생성

💡 dxb-css vs Tailwind CSS 빌드의 차이
Tailwind CSS (빌드): npm build 후 사용하지 않는 클래스 제거 → 최소 CSS 파일
  장점: 최종 파일 크기 작음 | 단점: 빌드 환경 필요, PHP 템플릿 동적 클래스 미지원

dxb-css (런타임): 빌드 없이 브라우저에서 필요한 CSS만 즉석 생성
  장점: 빌드 환경 불필요, PHP 동적 클래스 지원, 서버사이드 렌더링 친화적
  단점: 초기 실행 비용 (Trie 최적화로 최소화)


6장. 이미지 최적화 — DxThumb

DxThumb은 게시글 첨부 이미지를 자동으로 리사이징하여 썸네일을 생성합니다. 원본 이미지를 그대로 사용하면 페이지 로드 속도가 느려지므로, 목록 화면에는 작은 썸네일만 전송합니다.


6.1 썸네일 생성 흐름

// 게시글 작성 완료 → DxThumb::autoFromPost() 자동 호출

흐름:
1. 첨부 이미지 파일 목록 조회 (post_files 테이블)
2. 첫 번째 이미지 파일 선택 (또는 본문 img src 추출)
3. DxThumb::create($srcPath, $boardKey, $options) 실행
   ├ imagecreatefromjpeg/png/gif/webp 로 원본 로드
   ├ 리사이즈 모드에 따라 처리
   └ imagejpeg($dst, $savePath, 92)  JPEG 품질 92로 저장
4. data/uploads/thumbnails/{board_key}/ 에 저장
5. dx_posts.thumbnail 컬럼 업데이트
6. dx_post_files.thumb_path, is_thumb=1 업데이트


6.2 리사이즈 옵션

type 동작 사용 예
crop (기본) 지정 크기로 크롭. 비율 유지 후 중앙 잘라냄 갤러리 목록의 정사각형 썸네일
fit 비율 유지하며 지정 크기 안에 맞춤. 여백 없음 세로 길이 다양한 이미지
width 가로 크기만 지정. 세로는 비율에 따라 자동 뉴스형 레이아웃
height 세로 크기만 지정. 가로는 비율에 따라 자동 배너형 이미지


6.3 게시판별 썸네일 설정

// 관리자 → 게시판 설정 → 썸네일 탭

// dx_boards 테이블 컬럼
thumb_type  = "crop"   // fit | crop | width | height
thumb_w     = 300      // 썸네일 가로 (px)
thumb_h     = 200      // 썸네일 세로 (px)

// handler.php에서 게시글 저장 후 자동 호출
$thumbOptions = array(
    "type" => $board["thumb_type"],  // crop
    "w"    => (int)$board["thumb_w"], // 300
    "h"    => (int)$board["thumb_h"], // 200
);
DxThumb::autoFromPost($postId, $board["board_key"], $thumbOptions);


6.4 JPEG 품질 92

// DxThumb::create() 내부
$saved = @imagejpeg($dst, $savePath, 92);
// v5.5.6: 품질 85 → 92로 향상

// JPEG 품질 비교
// 100: 무손실 수준, 파일 크기 매우 큼 (원본 대비 2~3배)
// 92:  육안으로 무손실에 가까움, 원본 대비 40~60% 크기  ← DXCMS 기본
// 85:  약간의 압축, 육안으로 거의 차이 없음, 원본 대비 30~50% 크기
// 70:  눈에 띄는 압축 아티팩트, 원본 대비 20~30% 크기

// 300×200 원본 사진 기준 예상 크기
// 원본 PNG:    ~150KB
// 썸네일 Q92: ~25~40KB   ← 약 75% 절감
// 썸네일 Q85: ~18~30KB   ← 약 80% 절감


6.5 썸네일 보안 (.htaccess)

// DxThumb::create() — 썸네일 폴더에 .htaccess 자동 생성
$ht = $thumbDir . "/.htaccess";
if (!file_exists($ht)) {
    file_put_contents($ht, "Options -Indexes\n");
}

// Options -Indexes 효과:
// /data/uploads/thumbnails/free/ 직접 접근 시
// → 파일 목록 표시 대신 403 Forbidden
// → 어떤 파일이 있는지 외부에서 알 수 없음

// 파일명도 랜덤화 (random_bytes 또는 openssl)
$saveName = bin2hex(random_bytes(8)) . ".jpg";
// 예: a1b2c3d4e5f6g7h8.jpg
// → URL 예측 불가 → 직접 링크 공유 방지


6.6 지원 이미지 포맷

포맷 처리 방법
JPEG (.jpg, .jpeg) imagecreatefromjpeg() — 모든 PHP GD 환경 지원
PNG (.png) imagecreatefrompng() — 모든 PHP GD 환경 지원
GIF (.gif) imagecreatefromgif() — 모든 PHP GD 환경 지원
WebP (.webp) imagecreatefromwebp() — PHP 7.0+, GD 2.1.0+ 필요
BMP (.bmp) imagecreatefrombmp() — PHP 7.2+ 필요

⚠️ GD 라이브러리가 없으면 썸네일 생성 불가
DxThumb::create()는 함수 첫 줄에서 imagecreatefromjpeg 존재 여부를 확인합니다.
없으면 null을 반환하고 썸네일 생성을 건너뜁니다.

GD 활성화 확인: phpinfo()에서 "GD Support: enabled" 확인
Linux 설치: apt-get install php-gd 또는 yum install php-gd
이미 설치되어 있다면 php.ini에서 extension=gd 주석 해제


7장. 업로드 파일 서빙

사용자가 업로드한 파일(첨부파일, 프로필 이미지 등)은 data/uploads/ 에 저장됩니다. .htaccess 에서 직접 서빙을 허용하지만, 다운로드 권한 확인이 필요한 파일은 PHP 다운로드 API를 통해 제공합니다.


7.1 직접 서빙 vs API 서빙

파일 유형 서빙 방식 이유
썸네일 이미지 직접 서빙 (data/uploads/thumbnails/) 모든 사람이 볼 수 있는 공개 이미지
프로필 이미지 직접 서빙 (data/uploads/profiles/) 공개 이미지
게시글 첨부파일 API 서빙 (/api/download?file_id=123) 권한/포인트/다운로드 횟수 제한 필요


7.2 다운로드 API (api/download.php)

// GET /api/download?file_id=123

// 처리 흐름
1. file_id로 dx_post_files 조회
2. 연결된 게시글의 dl_level 확인
   dl_level=0: 전체 허용
   dl_level=1: 로그인 필요
   dl_level=2: 관리자만
3. dl_point 확인 (포인트 차감 필요 시)
4. dl_limit 확인 (1인당 최대 다운로드 횟수)
5. dx_download_log INSERT (이력 기록)
6. download_count +1 업데이트
7. 파일 전송
   header("Content-Disposition: attachment; filename=원본파일명")
   header("Content-Type: " . mime_content_type($savePath))
   readfile($savePath)


7.3 업로드 폴더 구조 및 보안

data/uploads/
  ├── {board_key}/            — 게시판별 첨부파일 (랜덤 파일명)
  │   ├── 20260511_a1b2c3d4.jpg
  │   └── 20260511_e5f6g7h8.pdf
  ├── profiles/               — 프로필 이미지 (GD 리사이즈 400×400)
  └── thumbnails/             — 썸네일 이미지
      └── {board_key}/
          ├── .htaccess       — Options -Indexes (폴더 목록 숨김)
          └── a1b2c3d4e5f6.jpg

// 업로드 파일 보안 설계
// 1. 랜덤 파일명: 원본 파일명 예측 불가
// 2. PHP 확장자 차단: .php, .asp 등 실행 파일 업로드 불가 (Secure::validateUpload)
// 3. MIME 검증: 실제 파일 내용이 MIME 타입과 일치해야 함
// 4. 매직바이트 검증: 이미지 위장 파일(image.php.jpg) 탐지
// 5. Options -Indexes: 폴더 목록 외부 노출 차단


8장. 테마 CSS 아키텍처

기본 테마(default)의 CSS는 여러 파일로 분리되어 역할별로 관리됩니다. PHP CSS 변수를 통해 관리자가 설정한 색상을 동적으로 적용합니다.


8.1 CSS 로딩 순서 (main.php)

// 1. CDN 폰트 (Pretendard, Montserrat)
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/.../pretendard.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/...Montserrat...&display=swap" rel="stylesheet">

// 2. Font Awesome 6 (아이콘)
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/.../font-awesome/6.5.2/css/all.min.css">

// 3. dx-utils.css (정적 Tailwind-like 유틸리티)
<link rel="stylesheet" href="/assets/css/dx-utils.css?v=8.1.0">

// 4. style.css (테마 전용 CSS)
<link rel="stylesheet" href="/themes/default/layout/style.css?v=8.1.0">

// 5. 인라인 <style> — PHP 동적 CSS 변수
<style>
:root {
  --p: <?php echo $_primaryColor; ?>;  /* 관리자 설정 주색상 */
  --bg-body: #f1f3f5;
  --bg-card: #ffffff;
  --font: "Pretendard", sans-serif;
}
body.dark {
  --bg-body: #0f172a;
  --bg-card: #1e293b;
}
</style>

// 6. dxb-css.js (런타임 유틸리티 CSS, dx_head 훅)
<script src="/assets/js/dxb-css.js"></script>


8.2 CSS 변수 시스템

// themes/default/layout/style.css 에서 CSS 변수 활용

.dx-header {
    background: var(--bg-header);
    border-bottom: 1px solid var(--border);
    color: var(--text-main);
}

.dx-btn-primary {
    background: var(--p);     /* 관리자 설정 주색상 자동 적용 */
    color: #fff;
}

// PHP에서 주색상 값 주입
$_primaryColor = dx_theme_option("primary_color", "#1a73e8");
// 관리자가 #1a73e8 → #6366f1로 바꾸면
// 버튼, 링크, 강조 색상이 모두 자동으로 바뀜


8.3 다크모드 CSS 처리

// 다크모드 전환: body.dark 클래스 토글

// style.css
:root {
    --bg-body: #f1f3f5;
    --text-main: #1e293b;
}
body.dark {
    --bg-body: #0f172a;
    --text-main: #f1f5f9;
}

// JavaScript 토글 (main.php)
document.body.classList.add("dark");    // 다크모드
document.body.classList.remove("dark"); // 라이트모드
localStorage.setItem("dx-theme", "dark"); // 상태 저장

// 재방문 시 복원
if (localStorage.getItem("dx-theme") === "dark") {
    document.body.classList.add("dark");
}


9장. JS 로딩 전략

JavaScript는 로딩 위치와 방식에 따라 페이지 렌더링 속도에 큰 영향을 줍니다. DXCMS는 각 JS 파일의 역할에 따라 로딩 위치와 방식을 구분합니다.


9.1 JS 파일별 로딩 위치

파일 로딩 위치 이유
dxb-css.js <head> (우선순위 1) 페이지 렌더 전 CSS 생성 필요 → FOUC 방지
jQuery <head> 다른 스크립트들이 의존. 먼저 로드 필요
Highlight.js <head> DOMContentLoaded 전에 코드 블록 강조 처리
style.css, dx-utils.css <head> 스타일 먼저 → 레이아웃 깜빡임 방지
dx-session-guard.js </body> 직전 인증 상태 확인, DOM 필요 없음. 렌더링 안 막음
dx-rescue.js </body> 직전 글쓰기 페이지만 조건부 로드
dx-draft.js </body> 직전 글쓰기 페이지만 조건부 로드
소켓 JS </body> 직전 로그인 사용자만 로드. 연결은 비동기


9.2 조건부 JS 로드

// 세션 가드: 로그인 상태일 때만 로드
<?php if (!empty($_isLogin)): ?>
<script>
window.DX_SESSION_GUARD = {
    isLogin  : true,
    baseUrl  : "<?php echo dx_base_url(); ?>",
    loginUrl : "<?php echo dx_base_url('auth/login'); ?>"
};
</script>
<script src="/assets/js/dx-session-guard.js?v=<?php echo DX_VERSION; ?>">
</script>
<?php endif; ?>

// 글쓰기/수정 페이지: write.php에서만 로드
<script>
window.DX_RESCUE_CONFIG = { ... };
</script>
<script src="/assets/js/dx-rescue.js?v=1.2.0"></script>

// 비로그인 사용자: 세션 가드, rescue, draft JS 로드 안 함
// → 불필요한 JS 요청 제거 → 페이지 로드 속도 향상


9.3 Google Analytics 조건부 로드

// main.php — 관리자에서 GA 코드 입력 시에만 로드
<?php
$_ga = dx_config("google_analytics", "");
if ($_ga):
?>
<script async
    src="https://www.googletagmanager.com/gtag/js?id=<?php echo $_ga; ?>">
</script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag("js", new Date());
  gtag("config", "<?php echo $_ga; ?>");
</script>
<?php endif; ?>

// "async" 속성: 스크립트 로딩이 HTML 파싱을 막지 않음
// → GA 스크립트가 느려도 페이지 렌더링은 계속 진행


10장. 정적 리소스 성능 최적화


10.1 Gzip/Brotli 압축 (Apache)

.htaccess에 명시적 압축 설정이 없지만, 서버 레벨 또는 .htaccess에서 설정 가능합니다. CSS/JS 파일은 압축 시 60~80% 크기 절감이 가능합니다.
# .htaccess에 추가 — CSS/JS/HTML Gzip 압축
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css
    AddOutputFilterByType DEFLATE application/javascript application/json
    AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>

# 또는 mod_brotli (Apache 2.4.26+)
<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/css
    AddOutputFilterByType BROTLI_COMPRESS application/javascript
</IfModule>

// 효과
// dx-utils.css:  8.6KB → ~2.5KB  (71% 절감)
// style.css:    ~30KB  → ~8KB    (73% 절감)
// dxb-css.js:  ~33KB  → ~10KB   (70% 절감)


10.2 브라우저 캐시 만료 설정 (Expires)

# .htaccess에 추가 — 정적 파일 브라우저 캐시 만료 설정
<IfModule mod_expires.c>
    ExpiresActive On
    # 이미지: 1년 캐시
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png  "access plus 1 year"
    ExpiresByType image/gif  "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    # CSS/JS: 1년 캐시 (버전 버스팅으로 갱신)
    ExpiresByType text/css                  "access plus 1 year"
    ExpiresByType application/javascript    "access plus 1 year"
    # 폰트: 1년 캐시
    ExpiresByType font/woff2                "access plus 1 year"
</IfModule>

// 버전 버스팅 (?v=8.1.0) 과 결합하면:
// 같은 버전: 1년간 캐시 → 네트워크 요청 없음
// 버전 업데이트: URL 변경 → 새 파일 다운로드


10.3 리소스 최적화 체크리스트

  • [ ] .htaccess 적용 확인: /core/Secure.php 직접 접근 시 403 반환하는지 테스트
  • [ ] 버전 버스팅 확인: 브라우저 개발자 도구 → Network에서 CSS/JS URL에 ?v= 파라미터 있는지
  • [ ] Gzip 압축: 개발자 도구 → Network → 응답 헤더의 Content-Encoding: gzip 확인
  • [ ] 썸네일 확인: 게시글 작성 후 목록에서 썸네일 이미지 정상 표시 여부
  • [ ] GD 라이브러리: phpinfo()에서 GD Support: enabled 확인
  • [ ] preconnect: 개발자 도구 → Network 탭에서 폰트 CDN 연결 시간 확인
  • [ ] CDN 차단 환경: 방화벽으로 CDN 접근이 막힌 경우 자체 호스팅으로 전환
  • [ ] DX_VERSION 업데이트: 배포 시 index.php의 DX_VERSION 상수 값 올리기


10.4 성능 측정 도구

// 1. PageSpeed Insights (Google)
// https://pagespeed.web.dev/
// 정적 리소스 관련 권장사항:
//   - "Serve static assets with an efficient cache policy"
//   - "Eliminate render-blocking resources"
//   - "Properly size images"

// 2. Chrome 개발자 도구 → Network 탭
// - 각 파일 크기, 로드 시간 확인
// - "Size" 컬럼: 전송 크기 (압축 후) / 실제 크기
// - "(from cache)" 표시: 캐시 히트
// - "(disk cache)" vs "(memory cache)" 구분

// 3. DX_START 처리 시간 (PHP 레벨)
// extend/bottom/ 에서 측정
$elapsed = round((microtime(true) - DX_START) * 1000, 2);
// 정적 파일은 측정 대상 아님 (PHP 미경유)
// 이 값은 PHP 처리 시간만 측정
 

댓글0

로그인 후 댓글을 작성할 수 있습니다.
7. 테마 DXCMS 테마 개발 AI 프롬프트 스킬과 멀티사이트 체험 2026.05.23 6. 게시판 DXCMS 게시판 스킨 만들기 Prompt Skill 2026.05.23 16. 이슈 가이드 막코딩 필수 규칙 2026.05.21 16. 이슈 가이드 그누보드의 `_common.php` 처럼, `dx_load.php` 한 줄로 DXCMS의 모든 기능을 사용하는 방법입니다. 2026.05.21 15. 마켓 개발자 가이드 마켓 다운로드 보호 설정 가이드 2026.05.20 6. 게시판 게시판 여분 필드 (Board Extra Fields) 사용 가이드 2026.05.19 14. 데이터베이스 Database 직접 쿼리 개발 2026.05.19 14. 데이터베이스 DB스키마 2026.05.12 13. 보안 기본 보안 구조 2026.05.10 12. 성능 / 최적화 트래픽 대응 2026.05.10 12. 성능 / 최적화 정적 리소스 관리 2026.05.10 12. 성능 / 최적화 캐싱 전략 2026.05.10 11. 인증 / 로그인 시스템 세션 처리 구조 2026.05.10 11. 인증 / 로그인 시스템 인증 흐름 2026.05.10 11. 인증 / 로그인 시스템 소셜 로그인 2026.05.10 11. 인증 / 로그인 시스템 일반 로그인 2026.05.10 10. 마이페이지 마이페이지 구조 2026.05.10 9. 채팅 채팅 제작 2026.05.10 9. 채팅 채팅 구조 2026.05.10 3.8 Extend 구조 Extend 실제 소스 코드 완전 분석 • 12가지 실전 사례 2026.05.02
31
전체 회원
502
전체 게시글
767
전체 댓글
441
오늘 방문
33,173
전체 방문
2
현재 접속
인기글 7일 이내
최신글
최신댓글
목록