스크롤에 따른 이미지, 아이콘 회전시키기

친한 동생이 사이트에 적용하고 싶은 기능이 있었다.

스크롤을 하는 것에 따라서 사이트의 중간에 이미지를 하나 놔두고 
회전시키고 싶다는 것이다.

관련 참고 사이트는 : [NONFICTION 논픽션](https://nonfiction.kr/)

이것이었고,

관련해서 일단 image를 하나 넣어서
해당 이미지를 특정하고

<style>
    #id {
        position:fixed;
        bottom: 200px;
        margin: auto;
        z-index: 100;
    }
</style>

이렇게 head에 스타일을 준 뒤에 
회원을 줄 수 있는 스크립트를 추가해주었다.

<script>

    function scrollRotate() {
        let image = document.getElementById("id");
	    image.style.transform = "rotate(" + window.pageYOffset/2 + "deg)";
    }

	window.addEventListener('scroll',scrollRotate);

</script>

이렇게 해서 해결!
생각보다 간단하게 해결해서 나처럼 고생하는 사람들이 있을 것 같아 공유한다.

참고 link: [Frontend Shorts: How to rotate the element on scroll with JavaScript - DEV Community](https://dev.to/foundsiders/frontend-shorts-easily-rotate-the-element-on-scroll-with-javascript-1g4p)

  1. 2021.06.20 18:06

    비밀댓글입니다

    • 2021.06.20 20:39

      비밀댓글입니다

그동안 서버만 하다가 회사 프로젝트로 다노 매거진을 개편하는 업무를
진행하게 되었다.

관련 글 : daeguowl.tistory.com/185

 

다노 매거진 개편 프로젝트를 완료하며...

올 한해 여러가지 프로젝트를 진행했지만, 정말 나의 피, 땀, 눈물이 들어간 프로젝트를 마무리하며 회고글을 적어보고자 한다. 사건의 발단 : 현재 다노에서는 매주 or 2주 단위로 1on1 이라는 것

daeguowl.tistory.com

처음 사용해보는 React로 웹을 개발하고 나니 SPA(single page application)라는 말을 듣게 되었다.

즉 한 페이지에서 각각의 컴포넌트들만 랜더링 되면서 사용된다는 것인데,
처음에는 이게 무슨 말인지도 몰랐다.

여러 페이지를 이동하는 것 같은데, 왜 싱글 페이지라고 할까...??

즉 react의 경우 build 된 이후의 파일을 보면 body 부분이
<body></body>로 텅 비어있고, js로 이 body부분을 변경해 가면서
rendering 된다는 것이다.

다만 이렇게 되면 우리의 사이트를 크롤링해가는 봇들이,
우리 사이트를 빈사이트로 인식하게 된다.

매우 큰 이슈다...

여러 부분의 단점이 있지만 일단 크게 와닿는 부분은 
해당 페이지가 rendering 되기 전의 정보를 가져가다 보니,
공유하기 등을 하였을 때 해당 페이지의 링크만 공유되는 불상사가 일어났다.

정말 간단하게는 대표 이미지와 description을 정해놓고 meta tag에 넣어주면 된다.

간단하게는 이렇게 대표 이미지와 글을 넣어 놓을 수 있다.

사실 이렇게만 해도 대부분의 회사에서는 해결이 될텐데, 문제는 특정 상품의 링크를 공유했을 때
해당 부분이 나왔으면 좋겠다는 것이다.(제품별로)

사실 다노샵처럼 여러 제품이 있는 경우가 아니라면 
각각의 특정 페이지별로 SSR하는 것은 이미 잘 만들어진 러이브러리들이 많다.

react-helmet이라는 라이브러리인데, 이정도의 SEO가 필요하다면,
아래 글을 참고 하면 된다.

velog.io/@byseop/SPA%EC%97%90%EC%84%9C-%EC%84%9C%EB%B2%84%EC%82%AC%EC%9D%B4%EB%93%9C-%EB%A0%8C%EB%8D%94%EB%A7%81%EC%9D%84-%EA%B5%AC%EC%B6%95%ED%95%98%EC%A7%80-%EC%95%8A%EA%B3%A0-SEO-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0

 

SPA에서 서버사이드 렌더링을 구축하지 않고 SEO 최적화하기

얼마 전 create-react-app 기반의 SPA 프로젝트에서 빠른시간내에 SEO를 적용해야 하는 일이 있었습니다. 제가 아는것은 SPA의 SEO는 next.js 혹은 gatsby 를 이용하여 개발하거나, 직접 서버사이드 렌더링(Se

velog.io


아무튼 내가 하고 싶은 것은 각각의 제품별로 SEO를 진행하고 싶은 것이었다.
(일단 여기서 다룰 것은 링크를 공유했을 때 각각의 제품에 맞는 제품명과 사진을 가져가서 보여주는 것이었다.)

확인해보니 쿠팡과 gmarket은 잘 구현되어 있다.

다노의 경우 위의 대표링크를 공유했을 때처럼 제품을 공유해도 SSR이 되어 있지 않아서,
default로 meta tag에 설정해놓은 이미지와 설명이 나오고 있었다.

사실 여러가지 방법들이 있겠지만, 
프론트개발자분들은 너무 리소스가 없으셨고,
서버단에서 이 문제라도 해결할 수 있는 방법이 없을까 모색하였다.

일단 크롤러 봇이 프론트의 build된 코드에 접근해 버리는 순간
어떤 방법을 써도 SSR을 하는 것이 어려웠다.

그래서 프론트의 build된 코드로 접근하기 전에 붙어 있는 nginx에서
봇인지를 판단해서 우리가 만들어 놓은 페이지로 redirect를 하면 되지 않을까?라는 
아이디어를 내놓게 되었다.

전체적인 그린 그림은 아래와 같다.

검정색은 일반적인 흐림이라면, 빨간색은 봇이 들어올 때의 움직임을 표현하였다.
일단 봇이 user-agent를 잡아서 봇인 경우, build된 코드로 넘기지 않고 서버의 새로운 api 로 redirect 시키는 것이다.

그리고 그 api는 서버에서 원하는 meta tag를 넣은 templates를 rendering 시켜주자고 
계획을 세웠다.

이렇게 게획을 세우고 나니, 한번 해봐도 될 것 같다는 생각이 들어 바로 진행하였다.

먼저 django에 rendering 해줄 수 있는 templates를 하나 만들었다.
(현재 다노에서는 python과 django로 서버를 구성하고 있다.)

간단하게 표현하면 아래와 같다.

# product_info로 rendering할 때 data를 넘겨줄 예정

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
	<meta property="og:title" content="{{ product_info.title }}"/>
        <meta property="og:image" content="{{ product_info.image }}"/>
        <meta property="og:description" content="{{ product_info.description }}"/>
        <title>Title</title>
    </head>
    <body>
        
    </body>
</html>


(회사마다 어떤 걸 SEO하고 싶은지는 다를 수 있으니 가장 기본에 충실한 것만 여기서 표현하고자 한다.)

이렇게 rendering 해줄 수 있는 templates를 하나 만들고 이제 해당 templates를 rendering 해줄 수 있는 
view를 만들었다. 이것도 매우 간단하다.

class ProductSeoView(View):

    def get(self, request, **kwargs):

        product_id = int(request.GET.get('product_no', -1))
        
        # 각자 SEO하고자 하는 코드를 적으면 된다. 아래코드는 예시로 표현하였다.

        product = Product.objects.get(id=product_id)
        title = product.title
        description = product.description
        image = product.image
        url = product.url

	product_info = {
            'title': title,
            'description': description,
            'image': image,
            'url': url,
        }

        return render(request, 'seoproduct.html', {'product_info': product_info})

(해당 코드는 예시로 적은 코드이다)

view 역시 굉장히 간단하다. 
제품 번호를 받아서 거기에 맞는 제품 정보를 가지고 와서, product_info에 넣고, 해당 템플릿을 랜더링 시켜주는 것이다.

그럼 해당 view로 요청이 왔을 때 아까 위해서 만든 템플릿이 해당 product_info 정보를 받아서 랜더링 되게 된다.

이제 해당 view를 띄어줄 수 있도록 url만 하나 뚫어주면 된다.
기존 요청 오는 api아래에 seo라는 api 하나만 더 뚫어주면 된다.

# 기존 product view
re_path(r'^/product?$', ProductView.as_view(), name='product_view'),

# product seo를 위한 view
re_path(r'^seo/product?$', ProductSeoView.as_view(), name='product_seo_view'),


이렇게 되면 서버쪽은 모두 준비가 되었다.

그럼 runserver를 돌린 뒤에 해당 api로 요청을 보내보면 빈페이지가 나와 있어야 한다.
그리고 추가적으로 소스코드를 보면 meta tag에 해당 내용이 담겨 있어야 한다.

이것만 확인 되었으면 이제는 프론트 nginx의 설정을 건드려야 한다.

프론트 서버의 nginx conf파일을 변경하자.
회사마다 모두 다를 것이라고 생각하기 때문에 포인트만 잡고자 한다.

여기서 하고자 하는 것은 바로 카카오톡 봇을 파악해서,
바로 위의 SEO를 위한 서버 URL로 redirect 시키는 것이다.

프론트 서버의 nignx의 location 설정을 변경시켜주면 된다.

location /product { 
	# 여기는 잡고자 하는 bot의 user_agent를 모두 넣으면 된다.
	# 지금 여기서는 kakao talk만을 기준으로 한다.

    if ($http_user_agent ~* "kakaotalk-scrap") { 
      	 rewrite ^/(.*)$ [api_server_url]/seo/$1 permanent;
    }
    
}

=> 프론트의 build된 코드를 서빙하는 것이 아니라 봇일 경우 api_server의 nginx로 seo라는 경로를 앞에 추가하여 
보내도록 하였다.

이렇게 한 뒤에는
nginx -t
nginx -s reload를 통해서 nignx를 다시 load시켜준다.

이제 테스트를 진행해보자!!

카카오톡에 seo를 진행하였던 링크를 복사해서 붙여 넣으면 된다.
그러면 카카오봇이 해당 사이트의 정보를 가지고 오기 위해 접근할 것이다.
(혹시라도 있을 캐시를 삭제해주는 것이 좋다. 카카오 dev에서 삭제가 가능하다)

그리고 프론트 nignx의 로그를 보면

GET /product?product=486 HTTP/1.1" 301 194 "-" "facebookexternalhit/1.1; kakaotalk-scrap/1.0;

오 get요청이 왔고 301로 제대로 redirect 되었다는 표시가 나온다!

이제 서버쪽 nignx의 로그를 보면

seo/product/?product_no=526 HTTP/1.1" 200 "https://danoshop.net/product/?product_no=526 "facebookexternalhit/1.1; kakaotalk-scrap/1.0

기존에 danoshop 요청 url이 seo라는 url로 붙어서 다시 들어온 것을 볼 수 있다.

그래서 그 결과는 과연 어떻게 되었을까?

기존에는

이렇게 상품을 공유해도 default 이미지가 나오던 것이
각각의 상품에 맞게 잘 나온다!!

이렇게 이쁘게 잘 되어 나온다!


이것으로 1차 목표하였던 카카오톡에 개별 링크를 공유했을 때
default meta tag가 아닌 각각의 사진과 text가 나오도록 작업을 진행하였다.
프론트 개발자분들의 리소스가 부족한 상황에서
서버 개발자인 내가 할 수 있는 부분에서 개선을 해보았다.

다만 작업을 하면서 추가적으로 고려해야 하는 부분도 발견하였다.
바로

[높은 페이지랭크를 가진 URL은 검색결과에서 더 상위에 노출 것입니다. 따라서 높은 페이지랭크를 가진 URL을 구입하여 컨텐트만 광고페이지로 이동시키는 부적절한 사례가 증가하고 있습니다. 이 때문에 구글은 302를 자주 사용하거나 부적절하게 사용할 경우 기존 사이트랭크에 상당한 패널티를 부여합니다.]

출처: https://nsinc.tistory.com/168 [NakedStrength]

의 글이다. 봇들이 접근할 때, 302로 redirect시키는 경우 평가를 안 좋게 한다는 글도 보았다.
그리고 이미 구글봇은 js를 읽을 수 있다고 하니, 따로 해당 부분을 적용할 필요는 없어 보인다.

따라서 각자의 상황에 맞게 해결을 하면 될 것 같다.

이렇게 해결을 한 글은 없는 것 같아
누군가에게는 도움이 되었으면 하면서 글을 남긴다.

  1. Websterking 2021.02.23 08:00 신고

    정말 유익하네요 생각해보지 못한 방향입니다 해봐야겠네요 ㅎㅎ 감사합니다

    • 2021.02.23 12:23

      비밀댓글입니다

    • 2021.02.24 09:24

      비밀댓글입니다

    • 2021.03.11 12:08

      비밀댓글입니다

react에서 datetime을 한글 "2020년 11월 10일"과 같은 형태로 변경해야하는 이슈가 있었다.
(디자이너 분이 2020년 11월 10일과 같이 해주셨는데... 바로 걸렸다ㅠㅠ)

여러 가지 방법을 찾아보다가 정말 초간단 변경할 수 있는 방법을 찾아서 공유한다.
바로 moment js 라이브러리를 사용하는 것인데, 
정말 내가 원하는 모든 형태로 구현할 수 있다.

긴말 할 것 없이.. 코드로 간단하게 해보면

export function change_date(published_at){
    var moment = require('moment');

    const publish_date = moment(published_at).format('YYYY년 MM월 DD일')
    return publish_date
}

 

이렇게 하면 published_at으로 받은 datetime을 원하는 format으로 변경할 수 있다.
예시를 보면 대부분 .format('YYYY-MM-DD')와 같은 형태로 있어서 이렇게만 쓸 수 있는줄 알았는데,
내가 원하는 어떤 형태로도 변경할 수 있다.

나와 같이 고생하는 사람이 없기를 바라면서 적어본다!

 

  1. sear24 2020.11.12 22:05

    많은분들에게 좋은 정보가 되었으면 좋겠네요.. ˊ◡ˋ

react 웹폰트 초간단 적용하기(react create app 사용시)

정말 초간단 적용 방법입니다. 다른 것 설치하고 할 필요 없습니다.

 

제 로컬에는 폰트가 잘 깔려 있어서 이상이 없었는데..
safari에서는 폰트가 깨지는 문제가 있었습니다.

바로 cdn으로 import해와서 쓰는 방법입니다.

 

보통 구글 폰트에서 바로 link ref를 가지고 와서 사용하는 방법이 일반적입니다.
이건 정말 link 한줄만 넣으면 되기 때문에 간단한 프로젝트를 하기에는 적합합니다.

해당 [Browse Fonts - Google Fonts](https://fonts.google.com/) 페이지에서

원하는 폰트를 선택한 뒤에 
해당 link를 가지고 와서 react public에 있는 index.html 의 head 부분에 넣어주면 끝이 납니다.

 

 

하지만 이렇게 되면 해당 폰트를 계속 import 해오는 시간이 걸리기 때문에
실제로 이루어지는 서비스에서는 사용하는 것을 추천드리고 싶지 않습니다. 

그래서 해당 폰트를 다운 받아서 저장한 뒤 사용할 예정입니다.
그럼 매번 import 해올 필요가 없기 때문에 성능을 매우 높일 수 있습니다.

 

먼저 사용할 폰트를 다운 받습니다.

여기서 주의 할 점이 있습니다.

제가 사용할 noto sans cjk kr 폰트를 기본으로 설명 드리겠습니다.
일단 원본 font 파일을 다운 받는 것은 여기서 진행하였습니다.
[Google Noto Fonts](https://www.google.com/get/noto/)

근데 여기서 다운 받아보니 폰트 용량만 하더라도 100mb가 훌쩍 넘는 것을 볼 수 있었습니다.
(이걸 거면 그냥 import해서 쓴다고 ㅠㅠ)

그래서 찾아보니 해당 폰트의 필요한 폰트들만 추려서 다시 만드는 서브셋 작업을 거치면 된다는 것을 알게 되었습니다.

[한글 웹 폰트 경량화해 사용하기 | Coderifleman's blog](https://blog.coderifleman.com/2015/04/04/using-korean-web-fonts/)

해당 글을 참고하여서 진행하였습니다.


여기 보면 사용할 text들을 넣는 것도 있는데 그것은 

폰트 모음집

[https://raw.githubusercontent.com/nacyot/korean_subset_glyphs/master/glyphs.txt]

여기서 다운 받아서 넣어주었습니다.

 

근데 결론적으로 이야기 드리면... 이렇게 일일이 파일 다운받고 하실 필요가 없습니다.

(일본어로 된 파일이라 무슨 말인지 모르겠는게 함정입니다...)

해당 폰트를 서브셋 작업을 해놓으신 분들이 있습니다. (ㅠㅠ 고마우신 분들)
[본고딕(Noto Sans CJK) 경량화 웹폰트 | NONRIA](https://nonria.com/post/104/)

여기서 해당 파일을 다운 받습니다. 
그럼 경량화된 웹폰트가 준비되었습니다.


이제 저희의 react에 적용해보겠습니다.

하 이것도 정말 여러 방법들이 많았는데, 결국 제가 찾은 제일 간단한 방법으로
적어보도록 하겠습니다.

먼저 src 아래에 
styles / fonts 디렉토리를 만듭니다.

그리고 거기 안에 경량화된 웹폰트를 다 복사해서 넣습니다.

 

그리고 이제 src root경로에 있는 index.css 파일을 열고선언을 해줍니다.

@font-face {
  font-family: 'Noto Sans CJK KR';
  font-style: normal;
  font-weight: 100;
  src: url("styles/fonts/NotoSansKR-Light.woff2") format('woff2'),
  url("styles/fonts/NotoSansKR-Light.woff") format('woff'),
  url("styles/fonts/NotoSansKR-Light.otf") format('truetype')
}

@font-face {
  font-family: 'Noto Sans CJK KR';
  font-style: normal;
  font-weight: normal;
  src: url("styles/fonts/NotoSansKR-Regular.woff2") format('woff2'),
  url("styles/fonts/NotoSansKR-Regular.woff") format('woff'),
  url("styles/fonts/NotoSansKR-Regular.otf") format('truetype')
}


@font-face {
  font-family: 'Noto Sans CJK KR';
  font-style: normal;
  font-weight: 500;
  src: url("styles/fonts/NotoSansKR-Medium.woff2") format('woff2'),
  url("styles/fonts/NotoSansKR-Medium.woff") format('woff'),
  url("styles/fonts/NotoSansKR-Medium.otf") format('truetype')
}

@font-face {
  font-family: 'Noto Sans CJK KR';
  font-style: normal;
  font-weight: bold;
  src: url("styles/fonts/NotoSansKR-Bold.woff2") format('woff2'),
  url("styles/fonts/NotoSansKR-Bold.woff") format('woff'),
  url("styles/fonts/NotoSansKR-Bold.otf") format('truetype')
}

 

각각의 말이 무엇이냐면 
font-family에  "Noto Sans CJK KR"
font-style에 "normal"
font-weight에 "100"이
들어가 있으면 해당 src에 있는 부분들을 적용해주겠다고 선언해준 것입니다.

 

그리고 src에 3개가 들어가 있는데 그 이유는

/* IE6-IE8 */

url('../fonts/test.eot?#iefix') format('embedded-opentype')/* Super Modern Browsers */

url('../fonts/test.woff2') format('woff2')

/* Modern Browsers */

url('../fonts/test.woff') format('woff')

/* Safari, Android, iOS */       

url('../fonts/test.ttf') format('truetype'),

 /* Legacy iOS */

url('../fonts/test.svg#OpenSans') format('svg');

 

여러 상황에 맞게 대응하기 위해서 입니다.

저렇게 하고 새로 고침하면!!

 

잘 적용된 것을 볼 수 있습니다.!

끝!

돔이란 :

웹브라우저와 관련된 객체들의 집합을 브라우저 객체 모델(BOM: Brower Object Model)이라고 부른다. 이 브라우저 객체 모델을 이용하여 Brower와 관련된 기능들을 구성한다. DOM은 BOM 중의 하나이다. DOM은 document object model의 약자이다. 문서 객체 모델인데, 문서 객체란 <html> < body>와 같은 html문서의 태그들을 javascript가 이용할 수 있는 개체로 만드는 것. 그것을 문서 객체라고 한다.

또 뒤에 model이 붙어 있는데 여기서는 문서 객체를 인식하는 방식정도로 해석하면 좋을 것 같다.

즉 웹브라우저가 html 페이지를 인식하는 방식이다. 


보통은 데이터가 변화하게 되면 양방향 바인딩으로 처리


변화(Mutation)
=> 특정 변화가 있으면 모델에 변화를 일으키고, view에 로직을 만들어준다.
그리고 화면에 다시 띄어준다.

 

페이스북에서는 다른 생각

만약에 변화가 일어나야 하면 mutation하지말고
기존에 있던 view를 날려버리고 새로 만들어버리면 어떨까?
=> 브라우저는 돔기반으로 작동하기 떄문에, 성능적으로 엄청난 문제가 있을 수 있다.

그래서 virtual dom이 나왔다.

가상의 돔이다. 

변화가 일어나면 브라우저의 돔에 새로운 것을 넣는 것이 아니라 javascript로 이루어진 가상의 돔에 랜더링을 하고 기존의 돔과 비교를 하고 정말 비교가 필요한 부분만 업데이트 한다 

 

 

 

어떤 변화가 일어나면 가상의 돔에 그린 다음에 실제 돔에 변화를 준다.

따라서 성능적으로 굉장히 우수하다.

 

리액트 다운로드 => npx create-react-app .
를 진행하면 아래와 같이 자동적으로 여러가지 폴더들을 생성해준다.

 

 

terminal을 열어서 npm run start를 하면 처음 리액트 화면을 만나 볼 수 있다. (감동 ㅠㅠ)

 

리액트의 시작은 package.json에 있는 

"scripts": {

"start": "react-scripts start",

이 부분이 시작된다.

react-script는 src 에 있는 index.js이고 index.js를 보면

ReactDOM.render(

<React.StrictMode>

<App />



</React.StrictMode>,

document.getElementById('root')

);

이런 부분이 있다. 이 말은 root id라는 부분에다가 

app이라는 부분을 채워넣어주겠다는 것인데, 여기서 app은 src/app.js이고

root id는 public/index.html에 가면 id="root"라고 설정해놓은 부분이 있다.

여기 div안에 app.js 파일이 들어간다고 생각하면 된다.

주의 => react의 webpack은 src 폴더 안에 있는 애들만 정리해서 주므로, public 폴더 안에 파일을 넣지 않도록 주의하자.

리엑트는 라이브러리다.
페이스북에서 만들어졌고, 2013년도 발표가 되었다.

컴포넌트로 이루어져 있어서, 모듈처럼 재사용성이 굉장히 뛰어나다.

virtual DOM => real Dom vs virtual Dom

real DOM은 list가 10개가 있는 것 중 1개가 변화가 일어나면, real Dom에서는 모든 것을 없애고 다시 가져온다.

virtual DOM에서는 하나만 가지고 올 수 있다.

어떻게 업데이트 된 것만 가지고 올 수 있냐면 => 스냅샷을 찍어놓고, 스냅샷을 찍어 둔 것과 차이를 분석해서, 바뀐 부분만 real DOM에서 바꾸어 준다.

Babel : 최신 자바스크립트 문법을 지원하지 않는 브라우저들을 위해서 
최신 자바스크립트 문법을 구형 브라우저에서도 돌 수 있게 변환시켜줌

webpack : 웹사이트를 만들 때 단순하게, 자바스크립트 몇개 파일 html이런게 아니라 라이브러리, 프레임워크를 사용하면서 굉장히 복잡하게 되었다. 이렇게 복잡하게 된 것을 webpack을 활용해서 간단하게 묶어 준다. => src폴더만 모아준다.

원래 리액트 앱을 처음 실행하기 위해선 webpack이나 babel 같은 것을 설정하기 위해
엄청 나게 많은 시간이 걸렸으나 지금은 create-react-app Command로 바로 시작할 수 있다.

react 다운 받을 때 => npx create-react-app .

npm vs npx의 차이는?
npm => 라이브러리들이 저장되어 있는 곳? npm run build package.json에 모두 저장되어 있다. (node package manger)

-g => global로 다운로드(usr/local/bin) / 따로 하지 않으면 local에 다운로드 된다.(node module)

옛날에 react를 다운 받을 때는 global에 다운받아서 사용했으나,
이제는 npx를 이용해서 그냥 npm registry에 있는 것을 사용할 수 있다.
disk space를 낭비하지 않고 항상 최신 버전을 사용할 수 있다.

 

+ Recent posts