# HTML/SEO 표준 가이즈
# 메타 태그 설정
> ## 메타 태그 설정
>
> 본 페이지는 퍼블리싱 작업중 HTML의 표준화를 위해 작성되었습니다.
#### **<title> 태그**
검색 결과에서 웹페이지의 제목으로 표시되며, 사용자에게 페이지 내용을 요약해 알려주는 중요한 요소입니다. 브라우저 탭에도 제목으로 표시됩니다
- 50~60자 이내로 간결하게 작성하며, 페이지의 핵심 내용을 포함합니다.
- 페이지마다 고유한 제목을 설정해 중복을 피하고, 중요한 키워드를 자연스럽게 포함합니다.
#### **<meta name="description"> 태그**
**페이지에 대한 요약 정보를 제공합니다. 검색 결과에 표시되며, 사용자에게 페이지 내용을 미리 알려주는 역할을 합니다.**
- 150~160자 이내로 작성해 검색 엔진에 잘 표시되도록 합니다.
- 매력적이고 클릭을 유도할 수 있는 문구를 포함합니다.
- 페이지의 주요 키워드를 포함하되, 내용과 부합해야 합니다.
#### **<meta name="robots"> 태그**
**검색 엔진이 페이지를 크롤링하고 인덱싱할 때의 지침을 제공합니다.**
- `index`/`noindex`: 페이지를 검색 엔진 인덱스에 포함할지 여부를 지정.
- `follow`/`nofollow`: 페이지 내 링크를 따라갈지 여부를 설정.
- 예를 들어, 인덱싱을 원하지 않는 페이지에 `noindex, nofollow`로 설정합니다.
#### **<meta name="keywords"> 태그**
**페이지와 관련된 키워드를 명시합니다. 현재는 대부분의 검색 엔진에서 무시되지만, 여전히 일부 SEO 도구에서 분석 요소로 사용됩니다.**
- 5~10개의 키워드를 쉼표로 구분하여 간략하게 작성합니다.
- 페이지의 핵심 주제와 관련된 키워드를 포함하되, 중복 키워드나 스팸성 키워드는 피합니다.
#### **<meta charset="UTF-8"> 태그**
**웹페이지의 문자 인코딩 방식을 지정합니다. UTF-8은 대부분의 언어와 특수 문자를 지원하기 때문에 널리 사용됩니다.**
#### **<meta name="viewport"> 태그**
**모바일 기기에서 화면의 초기 크기를 설정합니다. 반응형 웹 디자인을 구현할 때 필수적입니다.**
- `width=device-width`: 디바이스의 가로 폭에 맞게 화면 너비를 설정.
- `initial-scale=1.0`: 기본 확대 비율을 설정하여 페이지가 사용자 디바이스에 맞게 표시되도록 설정.
#### **<meta property="og:title"> 태그**
**페이지가 소셜 미디어에서 공유될 때 표시될 정보를 설정합니다. Facebook, Twitter와 같은 플랫폼에서 미리보기 정보로 활용됩니다.**
- **og**: 소셜 미디어에서 표시될 제목.
- **og**: 짧고 간결한 설명.
- **og**: 고해상도 이미지를 사용하여 시각적 매력을 높임.
- **og**: 해당 페이지의 정확한 URL을 사용.
#### **<link rel="canonical"> 태그**
**동일한 콘텐츠가 여러 URL로 접근 가능한 경우, 검색 엔진에 대표 URL을 명시하여 중복 콘텐츠 문제를 방지합니다.**
- 대표 콘텐츠로 인식되길 원하는 URL을 지정해 중복 페이지가 생성되는 것을 방지합니다.
- 특히 필터 또는 정렬 옵션을 통해 동일한 페이지가 여러 URL로 접근 가능한 경우 유용합니다.
#### **<meta http-equiv="X-UA-Compatible"> 태그**
**페이지가 Internet Explorer에서 특정 모드로 렌더링되도록 지시합니다.**
**최신 렌더링 엔진을 사용하도록 `IE=edge`로 설정하여 페이지가 최신 브라우저 표준에 맞게 표시되도록 합니다.**
# Owner Key 활용하여 외부 API 호출 예시
### **Opticalpos가 Host일 때**
- **Owner Key:** Opticalpos의 DB 정보가 담겨있는 암호화된 키 (`.env` 파일에 작성)
- **Main (Host):** Opticalpos
- **Guest:** Eyemsg
요청 주체 API 호출 대상 사용 DB Opticalpos Eyemsg API **Opticalpos DB** Eyemsg Opticalpos API **Opticalpos DB**
---
### **Eyemsg가 Host일 때**
- **Owner Key:** Eyemsg의 DB 정보가 담겨있는 암호화된 키 (`.env` 파일에 작성)
- **Main (Host):** Eyemsg
- **Guest:** Opticalpos
요청 주체 API 호출 대상 사용 DB Eyemsg Opticalpos API **Eyemsg DB** Opticalpos Eyemsg API **Eyemsg DB**
---
**핵심:**
- **Host의 DB 정보를 Owner Key로 관리**
- **Guest는 Owner Key를 이용해 Host의 API를 호출**
- **API 호출 시 항상 Host의 DB 정보를 사용**
# 제목 태그 (~)
#### 제목 태그 (``~``)
콘텐츠의 구조와 중요도에 따라 제목을 지정하여 계층 구조를 만듭니다.
- `` 태그는 페이지당 **하나만** 사용하며, **가장 중요한 ** 제목으로 설정.
- ``~`` 태그는 내용의 중요도에 따라 **순차적으로 ** 사용.
```html
페이지 메인 제목
부제목 1
소제목 1.1
부제목 2
```
#### **aria-label 속성**
##### **aria-label 속성이 필요한 경우**
**1. 레이블 텍스트가 없는 경우**
` ` 필드에 `` 요소가 없거나 시각적으로 레이블이 없는 경우에는 `aria-label`이 필요합니다. 예를 들어, 검색 상자, 텍스트 입력 창 등에 추가하여 필드의 목적을 설명할 수 있습니다.
```html
```
**2. 레이블을 커스터마이징하고 싶은 경우**:
레이블을 커스터마이징하거나 추가 설명이 필요할 때도 `aria-label`을 사용하여 스크린 리더가 필드의 의미를 더 잘 전달하도록 할 수 있습니다.
**3. 아이콘만 있는 입력 필드**
`aria-label`은 화면에 보이는 레이블이 없고 아이콘으로만 이루어진 입력 필드에 유용합니다.
```html
```
##### **aria-label 속성이 필요하지 않은 경우**
**1. 명확한 레이블이 있는 경우**
`` 요소로 이미 연결된 레이블이 있다면 `aria-label`을 추가할 필요없음
# aria-label 속성
#### **aria-label 속성**
##### **aria-label 속성이 필요한 경우**
**1. 레이블 텍스트가 없는 경우**
` ` 필드에 `` 요소가 없거나 시각적으로 레이블이 없는 경우에는 `aria-label`이 필요합니다. 예를 들어, 검색 상자, 텍스트 입력 창 등에 추가하여 필드의 목적을 설명할 수 있습니다.
```html
```
**2. 레이블을 커스터마이징하고 싶은 경우**:
레이블을 커스터마이징하거나 추가 설명이 필요할 때도 `aria-label`을 사용하여 스크린 리더가 필드의 의미를 더 잘 전달하도록 할 수 있습니다.
**3. 아이콘만 있는 입력 필드**
`aria-label`은 화면에 보이는 레이블이 없고 아이콘으로만 이루어진 입력 필드에 유용합니다.
```html
```
##### **aria-label 속성이 필요하지 않은 경우**
**1. 명확한 레이블이 있는 경우**
`` 요소로 이미 연결된 레이블이 있다면 `aria-label`을 추가할 필요없음
# lang 속성
#### **lang 속성**
lang 속성은 <html> 태그에 설정하는 것이 일반적입니다. 예를 들어, 문서가 한국어로 작성되었으면 lang="ko"를 지정하고, 영어로 작성된 경우 lang="en"을 지정합니다.
```html
```
lang 속성은**웹 접근성**을 개선하고 **SEO** 성능을 높이는 중요한 역할을 합니다. 따라서 HTML 문서의 `` 태그에 적절한 `lang` 속성을 설정하는 것이 좋습니다.
# label 태그
#### label 태그
<label> 요소는 일반적으로 <input>, <textarea>, <select>와 같은 폼 필드와 연결하여 사용합니다. <label>과 폼 필드는 for와 id 속성으로 연결됩니다.
```html
```
##### label 태그를 사용하지 않아도 되는 경우
- **시각적으로 명확하게 구분된 필드**: 폼 필드와 그에 대한 설명(예: 텍스트)이 분명히 인식될 수 있도록 디자인된 경우, 일부 사용자들은 `` 없이도 필드의 목적을 이해할 수 있습니다.
- **간단한 폼 필드**: 예를 들어, ` ` 필드가 이미 매우 직관적인 설명을 가지고 있고, 특별한 추가 설명이 필요하지 않다고 판단되는 경우에는 `` 없이도 사용할 수 있습니다.
##### label 태그를 사용해야 하는 경우
폼 필드와 관련된 **설명**이 필요할 때는 ``을 반드시 사용하는 것이 좋습니다. 특히 다음과 같은 경우에는 ``이 필요합니다:
- **필드에 대한 설명이 명확하지 않거나** 추가적인 설명이 필요한 경우: 예를 들어, `이메일 주소`, `비밀번호`와 같은 입력 필드는 반드시 해당 필드와 연결된 ``을 가져야 합니다.
- **스크린 리더 사용자**: 스크린 리더는 폼 필드를 읽을 때 `` 요소를 사용하여 필드에 대한 설명을 제공합니다. 따라서 모든 입력 요소에 대한 설명을 제공하는 ``을 사용해야 합니다.
# alt 속성
#### alt 속성
`alt` 속성은 **` `** 태그에서 사용됩니다.
이미지의 내용이나 기능을 간결하고 정확하게 설명하는 텍스트를 제공하는 것이 좋습니다.
```html
```
##### alt 속성을 사용하지 않아도 되는 경우
- **이미지가 순수한 장식용일 때**: 이미지가 페이지의 디자인적 요소로만 사용되는 경우, `alt` 속성은 빈 문자열(`alt=""`)로 설정합니다. 이는 스크린 리더가 이미지를 무시하게 합니다.
- **이미지가 기능을 갖지 않는 경우**: 이미지가 페이지에서 특별한 의미를 갖지 않거나 정보 전달을 위한 것이 아니라면, `alt` 속성을 생략할 수도 있습니다. 그러나 대부분의 경우 `alt` 속성을 사용하는 것이 권장됩니다.
# Apache를 통한 보안설정
# [SECURITY] http header의 cookie
**쿠키 이름** **역할** **Secure** 플래그 **HttpOnly** 플래그
`PHPSESSID` PHP 기본 세션 쿠키 **적용 안됨** **적용 안됨**
`XSRF-TOKEN` CSRF 보호를 위한 토큰 적용됨 **적용 안됨**
`Laravel 세션 쿠키:` Laravel 세션 쿠키 적용됨 적용됨
XSRF-ROKEN과 Laravel 세션 쿠키는 프론트에서 처리 가능
> ## **PHPSESSID의 Secure 플래그 추가 방법**
- #### **`php.ini` 설정 수정**
- #### **PHP 코드에서 설정
`session_start()`를 호출하기 전에 아래와 같이 설정합니다:
- #### **`.htaccess` 파일에서 설정**
- `.htaccess`를 사용하여 PHP 설정을 수정할 수 있습니다:
# [Security] Content-Security-Policy
Content-Security-Policy header is not set. It restricts resources the page can load and prevents XSS attacks.
seo 진단시 위와 같은 진단내용이 잡힌다면 아래 내용들을 확인해주세요.
> ### **Content-Security-Policy(CSP)란?**
CSP는 HTTP 응답 헤더 중 하나로, 브라우저가 페이지에 어떤 리소스를 로드할 수 있는지 제어하는 보안 정책을 정의합니다.
이를 통해 아래와 같은 보안 위협을 방지할 수 있습니다:
1. **XSS(크로스 사이트 스크립팅)**
공격자가 악성 JavaScript를 주입하는 것을 방지합니다.
2. **데이터 주입 공격**
악성 리소스(URL, CSS, 폰트 등)의 로드를 제한합니다.
3. **Clickjacking**
악성 iframe 삽입 등을 방지합니다.
---
> ### **Content-Security-Policy가 중요한 이유**
CSP가 없으면 다음과 같은 문제가 발생할 수 있습니다:
- 외부에서 악성 스크립트를 주입하여 **사용자 세션 탈취**, **피싱 공격** 가능.
- 비인가된 리소스가 로드되어 정보 유출 가능.
- HTTPS 환경에서도 보안이 취약한 HTTP 리소스가 로드될 가능성.
---
> ### **해결 방법: Content-Security-Policy 헤더 설정**
**Apache 설정을 통해 CSP 추가**
Apache에서 `Content-Security-Policy` 헤더를 설정하려면 `.htaccess` 파일이나 서버 설정 파일(`httpd.conf`, `apache2.conf`)에 다음 규칙을 추가합니다.
```bash
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com https://d3js.org https://cdnjs.cloudflare.com https://cdn.jsdelivr.net 'unsafe-inline' 'unsafe-eval'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net data:; connect-src 'self' https://www.google-analytics.com; img-src 'self' data: https://cdn.jsdelivr.net;"
```
default-src 'self'
현재 사이트 외의 다른 출처에서 리소스(스크립트, 스타일, 이미지)를 불러오지 못하게 방지합니다.
self
현재 사이트 자체에서 제공되는 스크립트만 허용합니다.
unsafe-inline
인라인 스크립트를 허용합니다 (보안상 추천되지 않음).
unsafe-eval
`eval()`이나 `setTimeout(string)` 같은 동적 코드 실행을 허용합니다.
data:
데이터 URI를 통해 폰트, 이미지 등의 데이터를 허용합니다.
\* 줄바꿈이 포함되면 500 server error가 발생할 수 있습니다.
**2. 프론트에서 미들웨어 추가 (라라벨 기준)**
1\. app/Http/Middleware에 새로운 class를 생성합니다.
2\. 아래와 같이 handle function을 추가합니다. $csp 변수 안에 내용은 직접 변경하셔야합니다.
```php
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$csp = "default-src 'self'; " .
"script-src 'self' https://www.googletagmanager.com https://d3js.org https://cdnjs.cloudflare.com https://cdn.jsdelivr.net 'unsafe-inline' 'unsafe-eval'; " .
"style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; " .
"font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net data:; " .
"connect-src 'self' https://www.google-analytics.com; " .
"img-src 'self' data: https://cdn.jsdelivr.net;";
$response->headers->set('Content-Security-Policy', $csp);
return $response;
}
```
3\. `app/Http/Kernel.php`에 미들웨어를 추가합니다.
```php
protected $middleware = [ \App\Http\Middleware\ContentSecurityPolicy::class, ];
```
> ## 권장사항
**엄격한 정책으로 전환**
- 초기에는 `unsafe-inline`을 사용할 수 있지만, 최종 배포시 제거하는 것이 좋습니다.
- `nonce` 또는 `hash` 기반 CSP를 사용하여 특정 스크립트만 허용하도록 설정하는 것이 좋습니다.
Nonce를 사용 (권장)
아래 코드는 nonce 적용에 대한 예시코드입니다.
\- apache 설정
```bash
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com https://d3js.org https://cdnjs.cloudflare.com 'nonce-abc123'; style-src 'self' https://fonts.googleapis.com https://cdn.jsdelivr.net 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.example.com; img-src 'self' data:;"
```
\- script 사용부분
```js
```
\- 동적 Nonce 생성
```php
```
```js
```
# [SECURITY] Header 보안설정하기
```bash
Header OK Notice Warning Critical Recommendation
---------------------------------------------------------------------------------------------
X-Frame-Options 0 0 77 0 X-Frame-Options header is not set.
It prevents clickjacking attacks when
set to 'deny' or 'sameorigin.'
X-Content-Type-Options
0 0 77 0 X-Content-Type-Options header is not set.
It stops MIME type sniffing and mitigates
content type attacks.
Referrer-Policy 0 0 77 0 Referrer-Policy header is not set.
It controls referrer header sharing and
enhances privacy and security.
Feature-Policy 0 0 77 0 Feature-Policy header is not set.
It allows enabling/disabling browser APIs
and features for security. Not important
if Permissions-Policy is set.
Permissions-Policy 0 0 77 0 Permissions-Policy header is not set.
It allows enabling/disabling browser APIs
and features for security.
Server 0 0 77 0 Server header is set to known 'Apache.'
It is better not to reveal used technologies.
Set-Cookie 70 65 70 0 Set-Cookie header for 'PHPSESSID' does not
have 'SameSite' flag. Consider using
'SameSite=Strict' or 'SameSite=Lax.'
Set-Cookie header for 'XSRF-TOKEN' does not
have 'HttpOnly' flag. Attacker can steal
the cookie using XSS. Consider using
'HttpOnly' when cookie is not used by JavaScript.
---------------------------------------------------------------------------------------------
```
> ### X-Frame-Options
#### 문제:
- X-Frame-Options 헤더가 설정되지 않음.
#### 해결:
Apache 설정 파일에 다음을 추가:
```bash
Header always set X-Frame-Options "DENY"
```
- **DENY**: iframe 로딩을 완전히 차단.
- **SAMEORIGIN**: 같은 도메인에서만 iframe 로딩 허용.
> ### **X-Content-Type-Options**
#### 문제:
- X-Content-Type-Options 헤더가 설정되지 않음.
#### 해결:
Apache 설정 파일에 다음을 추가:
```bash
Header always set X-Content-Type-Options "nosniff"
```
- MIME 타입 스니핑 방지.
> ### **Referrer-Policy**
#### 문제:
- Referrer-Policy 헤더가 설정되지 않음.
#### 해결:
Apache 설정 파일에 다음을 추가:
```bash
Header always set Referrer-Policy "no-referrer"
```
- **no-referrer**: 참조 헤더를 완전히 숨김.
- 다른 옵션: `strict-origin`, `strict-origin-when-cross-origin`, `same-origin` 등.
> ### **Feature-Policy / Permissions-Policy**
#### 문제:
- Feature-Policy 또는 Permissions-Policy 헤더가 설정되지 않음.
#### 해결:
Apache 설정 파일에 다음을 추가:
```bash
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
```
- **Permissions-Policy**: 특정 브라우저 기능(API) 사용을 제한.
- 예: `geolocation`, `camera`, `microphone` 등.
> ### **Server**
#### 문제:
- Server 헤더가 Apache로 설정되어 서버 정보가 노출됨.
#### 해결:
Apache 설정 파일에 다음을 추가:
```bash
ServerTokens Prod
ServerSignature Off
Header unset Server
```
- **ServerTokens Prod**: 최소한의 정보만 노출.
- **ServerSignature Off**: 오류 페이지에서 Apache 정보 숨김.
- **Header unset Server**: Server 헤더를 제거.
> ### **Set-Cookie**
#### 문제:
- `SameSite ` 및 `HttpOnly` 플래그가 없음.
#### 해결:
Apache 설정 파일에 다음을 추가:
```
Header always edit Set-Cookie ^(.*)$ "$1; SameSite=Strict; HttpOnly; Secure"
```
- **SameSite=Strict**: 쿠키를 외부 요청에 전송하지 않음.
- **HttpOnly**: JavaScript에서 쿠키 접근을 차단.
- **Secure**: HTTPS 요청에서만 쿠키를 전송.
> ### **전체 설정시**
```bash
# X-Frame-Options 설정
Header always set X-Frame-Options "DENY"
# X-Content-Type-Options 설정
Header always set X-Content-Type-Options "nosniff"
# Referrer-Policy 설정
Header always set Referrer-Policy "no-referrer"
# Permissions-Policy 설정
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=()"
# Server 정보 숨기기
ServerTokens Prod
ServerSignature Off
Header unset Server
# Set-Cookie 보안 설정
Header always edit Set-Cookie ^(.*)$ "$1; SameSite=Strict; HttpOnly; Secure"
```