IJMuAN02pVKE2uZHXG2JnZAHGfjon49GBXjko1rk
Bookmark

블로그스팟 링크 미리보기 | 오픈 그래프(OG) 만들기

워드프레스와 티스토리는 글 주소만 붙여넣기해도 링크 미리보기가 나오는데 블로그스팟은 왜 안나오는걸까요? 이 불편한 점은 단번에 개선할 수 있는 일명 링크 메이커 만드는 방법을 공유합니다.
워드프레스와 티스토리를 하다가 블로그스팟으로 넘어온 분들이 가장 적응 안되는 것이 바로 링크입니다.

글쓰기 입력창에 글 주소만 붙여넣기해도 링크 미리보기(오픈 그래프)가 나오는데 블팟은 없죠...

이번에 간단하게 보기 좋은 오픈 그래프(OG) 만드는 방법에 대해 알아보도록 하겠습니다.

오픈 그래프(Open Craph)란 무엇인가?

블로그 글쓰기 입력창에 주소를 붙여넣기하면 나타나는 오픈 그래프는 소셜 미디어나 메신저 등에서 웹페이지 링크를 공유할 때 미리보기를 생성하는데 사용되는 메타데이터를 의미합니다.


우리가 만든 웹페이지에는 이러한 오픈 그래프 태그라는 특별한 메타데이터가 들어있어서 글 주소를 붙여넣기하면 블로그(워드프레스, 티스토리) 플랫폼에서 이 정보를 읽어와서 제목, 설명, 대표이미지 등을 보여주는겁니다.
<!-- OPEN GRAPH --><b:comment>Facebook</b:comment><meta content='article' property='og:type'/>
<meta content='kr_KR' property='og:locale'/>
<meta content='en_US' property='og:locale:alternate'/>
<b:comment>Twitter</b:comment><meta content='summary_large_image' name='twitter:card'/>
제가 추천해드린 Igniel 님의 블로그스팟 테마도 HTML 편집에 들어가서보면 이러한 오픈 그래프 코드가 들어가 있는 것을 알 수 있습니다. 이 코드가 있기 때문에 카카오톡이나 다른 SNS에 붙여넣기하면 우리의 블로그스팟이 제대로 보여지게 되는거죠.

텍스트 링크보다 더욱 직관적이기 때문에 사용자의 클릭을 유도하고 어떤 내용인지 직관적으로 알수 있기 때문에 체류시간을 높이는데도 상당히 도움됩니다.

블로소득 오픈 그래프(ft.링크 메이커)

블로소득 오픈 그래프(일명 링크 메이커) 만드는 방법에 대해 알려드리기 전에 사용법부터 알려드릴께요!

링크 URL에 글 안에 넣을 내부/외부 링크 주소를 넣습니다. 그러면 자동으로 분석하죠!

간혹 썸네일이 안나오는 경우가 있는데 이럴 때는 직접 이미지 주소를 수정해줄 수 있습니다.

그리고 코드 생성을 누르면 코드가 만들어지는데 HTML 코드 복사를 누르고 블로그스팟 글쓰기 폼으로 돌아옵니다.

링크를 넣고 싶은 곳에 1234 라고 입력하고, 왼쪽 위의 연필 모양을 누릅니다.
(이렇게 하면 복잡한 코드 속에서 내가 입력하고자 하는 위치를 단번에 찾을 수 있습니다)

1234 숫자를 찾거나 Ctrl(컨트롤)+F를 눌러서 1234를 입력합니다. 그리고 그 곳에 방금 복사한 HTML 코드를 붙여넣기하면 끝납니다.

이제 완성된 링크의 모습을 확인해볼까요? 깔끔하죠? 나쁘지않습니다.

주의사항은 썸네일 이미지 크기를 축소시켜서 보여주기 때문에 사전에 이미지 최적화 과정은 반드시 진행하셔야 합니다. 이미지 사이즈 줄이는 방법 아시죠?

블로소득 링크 메이커 코드 안내

링크 메이커는 2개의 코드로 구성되어 있습니다. 테마에 적용하는 스타일 코드와 생성기 코드입니다.

스타일 코드는 테마에 붙여넣기하면 되고, 생성기 코드는 페이지에 붙여넣어서 활용하시면 됩니다.

1. 스타일 코드(테마 적용)

아래 코드를 복사해서 테마 > HTML 편집으로 들어가서 <!-- TITLE --> 앞에 붙여넣기 합니다.

(추가) Derelogy, Fiksioner 테마의 경우 </b:if> 뒤에 코드를 입력하시면 됩니다.

<!-- 링크 메이커 스타일 코드 -->
<style>.generated-link-card{display:flex;flex-direction:column;width:100%;max-width:600px;margin:15px auto;border:1px solid #e0e0e0;border-radius:12px;overflow:hidden;text-decoration:none;color:#333;box-shadow:0 4px 8px rgba(0,0,0,.05)}.generated-link-card .thumbnail{width:100%;height:auto}.generated-link-card .thumbnail img{width:100%;height:auto;display:block;object-fit:cover}.generated-link-card .content{padding:15px}.generated-link-card .title{font-size:16px;font-weight:700;color:#333;margin:0 0 5px;line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.generated-link-card .description{font-size:13px;color:#777;margin:0 0 10px;line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.generated-link-card .domain{font-size:12px;color:#777;font-weight:500}@media (min-width:481px){.generated-link-card{flex-direction:row;align-items:center}.generated-link-card .thumbnail{width:150px;height:150px;flex-shrink:0}.generated-link-card .thumbnail img{width:100%;height:100%;object-fit:cover}.generated-link-card .content{padding:15px;flex-grow:1}}</style>

2. 링크 생성기 코드(페이지 사용)

아래 코드를 복사해서 페이지로 이동한 후에 새 페이지를 누르고, 왼쪽 연필 아이콘을 누릅니다.
그리고 HTML 보기를 누른 후에 복사한 내용을 붙여넣기하고, 게시를 누릅니다.

자신의 블로그스팟 메뉴에 링크 생성기 바로가기를 해두고 편하게 사용하시면 됩니다.
<style>
  :root {
    --text-color: #333;
    --sub-text-color: #777;
    --border-color: #e0e0e0;
    --bg-color: #fff;
    --copy-btn-bg: #28a745;
    --copy-btn-hover: #218838;
    --reset-btn-bg: #dc3545;
    --reset-btn-hover: #c82333;
    --accent-color: #004dff;
  }
  .preview-generator-container {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    border: 1px solid var(--border-color);
    border-radius: 12px;
    background-color: var(--bg-color);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
  }
  .input-group {
    margin-bottom: 15px;
  }
  .input-group label {
    display: block;
    font-weight: 600;
    margin-bottom: 5px;
    color: var(--text-color);
  }
  .input-group input {
    width: 100%;
    padding: 10px;
    border: 1px solid var(--border-color);
    border-radius: 8px;
    box-sizing: border-box;
    font-size: 14px;
    transition: border-color 0.2s;
  }
  .input-group input:focus {
    outline: none;
    border-color: var(--accent-color);
  }
  .button-group {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
  }
  .button-group button {
    flex: 1;
    min-width: 100px;
    padding: 12px;
    border: none;
    border-radius: 8px;
    font-weight: 600;
    cursor: pointer;
    font-size: 15px;
    transition: background-color 0.2s, color 0.2s;
  }
  #generateButton {
    background-color: var(--accent-color);
    color: #fff;
  }
  #copyButton {
    background-color: var(--copy-btn-bg);
    color: #fff;
    display: none;
  }
  #resetButton {
    background-color: var(--reset-btn-bg);
    color: #fff;
    display: none;
  }
  #generateButton:hover { background-color: #0033aa; }
  #copyButton:hover { background-color: var(--copy-btn-hover); }
  #resetButton:hover { background-color: var(--reset-btn-hover); }

  #outputContainer {
    margin-top: 20px;
    padding: 15px;
    background-color: #f5f5f5;
    border-radius: 8px;
    display: none;
    overflow-x: auto;
  }
  #outputCode {
    white-space: pre-wrap;
    word-wrap: break-word;
    font-size: 13px;
    color: var(--text-color);
  }
  #previewContainer {
    display: none;
    margin-top: 20px;
    border-radius: 12px;
    overflow: hidden;
  }
  
  /* --- Final Output Card Styling --- */
  .preview-card {
    display: flex;
    flex-direction: column;
    width: 100%;
    margin: 0 auto;
    border: 1px solid var(--border-color);
    border-radius: 12px;
    overflow: hidden;
    text-decoration: none;
    color: var(--text-color);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
  }
  .preview-thumbnail {
    width: 100%;
    height: auto;
  }
  .preview-thumbnail img {
    width: 100%;
    height: auto;
    display: block;
    object-fit: cover;
  }
  .preview-content {
    padding: 15px;
  }
  .preview-title {
    font-size: 16px;
    font-weight: bold;
    color: var(--text-color);
    margin: 0 0 5px 0;
    line-height: 1.4;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  .preview-description {
    font-size: 13px;
    color: var(--sub-text-color);
    margin: 0 0 10px 0;
    line-height: 1.4;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  .preview-domain {
    font-size: 12px;
    color: var(--sub-text-color);
    font-weight: 500;
  }

  @media (min-width: 481px) {
    .preview-card {
      flex-direction: row;
      align-items: center;
    }
    .preview-thumbnail {
      width: 150px;
      height: 150px;
      flex-shrink: 0;
    }
    .preview-thumbnail img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    .preview-content {
      padding: 15px;
      flex-grow: 1;
    }
  }
</style>

<div class="preview-generator-container">
  <h3>LINK Maker - 블로그스팟 미리보기 생성기</h3>
  <p>블로그스팟 본문에 사용할 링크 미리보기 HTML 코드를 생성하세요.</p>
  <div class="input-group">
    <label>링크 URL</label>
    <input id="urlInput" placeholder="https://www.example.com" type="text" />
  </div>
  <div class="input-group">
    <label>제목 (자동입력)</label>
    <input id="titleInput" placeholder="링크 제목이 자동으로 입력됩니다." type="text" />
  </div>
  <div class="input-group">
    <label>설명 (자동입력)</label>
    <input id="descriptionInput" placeholder="링크 설명이 자동으로 입력됩니다." type="text" />
  </div>
  <div class="input-group">
    <label>썸네일 URL (자동/수동)</label>
    <input id="imageInput" placeholder="썸네일 URL이 자동으로 입력되지만, 직접 입력할 수도 있습니다." type="text" />
  </div>
  <div class="button-group">
    <button id="generateButton">코드 생성</button>
    <button id="copyButton">HTML 코드 복사</button>
    <button id="resetButton">초기화</button>
  </div>
  
  <div id="previewContainer">
    <a class="preview-card" href="#" id="finalPreview">
      <div class="preview-thumbnail">
        <img id="previewImage" src="" />
      </div>
      <div class="preview-content">
        <p class="preview-title" id="previewTitle"></p>
        <p class="preview-description" id="previewDescription"></p>
        <span class="preview-domain" id="previewDomain"></span>
      </div>
    </a>
  </div>

  <div id="outputContainer">
    <pre id="outputCode"></pre>
  </div>
</div>

<script>
  const urlInput = document.getElementById('urlInput');
  const titleInput = document.getElementById('titleInput');
  const descriptionInput = document.getElementById('descriptionInput');
  const imageInput = document.getElementById('imageInput');
  const generateButton = document.getElementById('generateButton');
  const outputContainer = document.getElementById('outputContainer');
  const outputCode = document.getElementById('outputCode');
  const copyButton = document.getElementById('copyButton');
  const resetButton = document.getElementById('resetButton');
  const previewContainer = document.getElementById('previewContainer');
  const finalPreview = document.getElementById('finalPreview');
  const previewImage = document.getElementById('previewImage');
  const previewTitle = document.getElementById('previewTitle');
  const previewDescription = document.getElementById('previewDescription');
  const previewDomain = document.getElementById('previewDomain');
  
  const proxyUrl = 'https://corsproxy.io/?';

  let fetchedData = {
    title: '',
    description: '',
    image: ''
  };

  urlInput.addEventListener('input', async () => {
    const url = urlInput.value.trim();
    if (url === '' || !url.startsWith('http')) {
      titleInput.value = '';
      descriptionInput.value = '';
      imageInput.value = '';
      return;
    }
    try {
      const response = await fetch(proxyUrl + encodeURIComponent(url));
      const text = await response.text();
      const parser = new DOMParser();
      const doc = parser.parseFromString(text, 'text/html');

      const getMeta = (name, property) => doc.querySelector(`meta[name="${name}"]`)?.content || doc.querySelector(`meta[property="${property}"]`)?.content || '';

      fetchedData.title = doc.querySelector('title')?.innerText || getMeta('og:title', 'og:title') || '제목을 가져올 수 없습니다.';
      fetchedData.description = getMeta('description', 'og:description') || '설명을 가져올 수 없습니다.';
      fetchedData.image = getMeta('og:image', 'og:image') || '';

      titleInput.value = fetchedData.title;
      descriptionInput.value = fetchedData.description;
      imageInput.value = fetchedData.image;

      // 미리보기 업데이트
      previewTitle.textContent = fetchedData.title;
      previewDescription.textContent = fetchedData.description;
      previewImage.src = fetchedData.image;
      
      previewContainer.style.display = 'block';

    } catch (error) {
      console.error('Error fetching URL:', error);
      titleInput.value = '정보를 가져오지 못했습니다.';
      descriptionInput.value = '';
      imageInput.value = '';
      previewContainer.style.display = 'none';
    }
  });

  // 사용자가 썸네일 주소를 직접 입력할 경우 미리보기를 즉시 업데이트
  imageInput.addEventListener('input', () => {
      previewImage.src = imageInput.value.trim() || fetchedData.image;
  });

  generateButton.addEventListener('click', () => {
    const url = urlInput.value.trim();
    const title = titleInput.value.trim();
    const description = descriptionInput.value.trim();
    
    // 사용자가 입력한 썸네일 주소가 있으면 사용, 없으면 자동으로 가져온 값 사용
    const image = imageInput.value.trim() || fetchedData.image;
    
    const domain = new URL(url).hostname.replace('www.', '');

    const generatedCode = `
<a href="${url}" target="_blank" class="generated-link-card">
  <img src="${image}" alt="${title}" class="thumbnail">
  <div class="content">
    <p class="title">${title}</p>
    <p class="description">${description}</p>
    <span class="domain">${domain}</span>
  </div>
</a>
`;

    outputCode.textContent = generatedCode.trim();
    outputContainer.style.display = 'block';
    copyButton.style.display = 'inline-block';
    resetButton.style.display = 'inline-block';
    
    // 최종 미리보기 업데이트
    finalPreview.href = url;
    previewImage.src = image;
    previewTitle.textContent = title;
    previewDescription.textContent = description;
    previewDomain.textContent = domain;
    previewContainer.style.display = 'block';
  });

  copyButton.addEventListener('click', () => {
    navigator.clipboard.writeText(outputCode.textContent).then(() => {
      copyButton.textContent = '복사 완료!';
      setTimeout(() => {
        copyButton.textContent = 'HTML 코드 복사';
      }, 2000);
    });
  });

  resetButton.addEventListener('click', () => {
    urlInput.value = '';
    titleInput.value = '';
    descriptionInput.value = '';
    imageInput.value = '';
    outputContainer.style.display = 'none';
    copyButton.style.display = 'none';
    resetButton.style.display = 'none';
    previewContainer.style.display = 'none';
    
    // 상태 초기화
    fetchedData = {
        title: '',
        description: '',
        image: ''
    };
  });
</script>

본문 음성듣기
음성선택
1x
* [주의] 설정을 변경하면 글을 처음부터 읽습니다.
댓글 쓰기
테마 디자인을 위해 댓글 영역이 표시되고 있습니다. 궁금하신 사항은 유튜브 채널 영상에 남겨주시면 답변드리겠습니다!