[javascript] “단일 페이지”JS 웹 사이트 및 SEO

요즘 강력한 “단일 페이지”JavaScript 웹 사이트를 만들 수있는 멋진 도구가 많이 있습니다. 내 생각에 이것은 서버가 API로 작동하도록하고 클라이언트가 모든 HTML 생성 항목을 처리하도록하여 올바르게 수행됩니다. 이 “패턴”의 문제점은 검색 엔진 지원이 부족하다는 것입니다. 두 가지 해결책을 생각할 수 있습니다.

  1. 사용자가 웹 사이트에 들어가면 서버가 클라이언트가 탐색 할 때와 정확히 같은 페이지를 렌더링하도록합니다. 따라서 http://example.com/my_path직접 방문하면 서버는 /my_pathpushState 를 통해 갈 때 클라이언트와 동일한 것을 렌더링합니다 .
  2. 서버가 검색 엔진 봇 전용 웹 사이트를 제공하도록합니다. 정상적인 사용자가 http://example.com/my_path서버를 방문 하면 웹 사이트의 JavaScript 무거운 버전을 제공해야합니다. 그러나 Google 봇이 방문하는 경우 서버는 Google에서 색인을 생성하려는 콘텐츠가 포함 된 최소한의 HTML을 제공해야합니다.

첫 번째 솔루션은 여기 에서 더 자세히 설명 합니다 . 나는 이것을하는 웹 사이트에서 일해 왔으며 그것은 좋은 경험이 아닙니다. DRY가 아니며 제 경우에는 클라이언트와 서버에 두 개의 다른 템플릿 엔진을 사용해야했습니다.

좋은 플래시 웹 사이트를위한 두 번째 해결책을 본 것 같습니다. 나는이 접근 방식이 첫 번째 접근 방식보다 훨씬 더 좋으며 서버에 올바른 도구를 사용하면 매우 고통없이 수행 할 수 있습니다.

그래서 내가 정말로 궁금한 것은 다음과 같습니다.

  • 더 나은 솔루션을 생각할 수 있습니까?
  • 두 번째 솔루션의 단점은 무엇입니까? Google이 어떤 식 으로든 Google 봇에 대해 일반 사용자와 동일한 콘텐츠를 제공하지 않는 것으로 확인되면 검색 결과에 처벌을 받습니까?


답변

# 2는 개발자에게는 “더 쉬울 수 있지만”검색 엔진 크롤링 만 제공합니다. 그렇습니다. Google이 귀하의 다른 콘텐츠를 제공하는 것을 알게되면 처벌을받을 수 있습니다 (전문가는 아니지만 그 일이 있다고 들었습니다).

SEO와 접근성 (장애인뿐만 아니라 모바일 장치, 터치 스크린 장치 및 기타 비표준 컴퓨팅 / 인터넷 가능 플랫폼을 통한 접근성)은 모두 유사한 기본 철학을 가지고 있습니다. 이러한 모든 브라우저에서 액세스, 조회, 읽기, 처리 또는 다른 방식으로 사용). 스크린 리더, 검색 엔진 크롤러 또는 JavaScript가 활성화 된 사용자는 모두 문제없이 사이트의 핵심 기능을 사용 / 색인화 / 인식 할 수 있어야합니다.

pushState내 경험에 따르면이 부담을 가중시키지 않습니다. 그것은 단지 웹 개발의 최전선에 “후 시간이 있다면”나중에 생각했던 것을 가져옵니다.

옵션 # 1에서 설명하는 것은 일반적으로 가장 좋은 방법이지만 다른 접근성 및 SEO 문제와 마찬가지로 pushStateJavaScript가 많은 앱 에서이 작업을 수행 하려면 사전 계획이 필요합니다. 그렇지 않으면 상당한 부담이 될 것입니다. 처음부터 페이지와 응용 프로그램 아키텍처에 구워 져야합니다. – 개조는 고통스럽고 필요한 것보다 더 많은 복제가 발생합니다.

나는 pushState최근 몇 가지 다른 응용 프로그램을 위해 SEO 와 함께 일해 왔으며 좋은 접근 방식이라고 생각하는 것을 발견했습니다. 기본적으로 항목 # 1을 따르지만 html / 템플릿을 복제하지 않는 이유가 있습니다.

대부분의 정보는 다음 두 블로그 게시물에서 찾을 수 있습니다.

http://lostechies.com/derickbailey/2011/09/06/test-driving-backbone-views-with-jquery-templates-the-jasmine-gem-and-jasmine-jquery/

http://lostechies.com/derickbailey/2011/06/22/rendering-a-rails-partial-as-a-jquery-template/

요점은 서버 측 렌더링에 ERB 또는 HAML 템플릿 (Ruby on Rails, Sinatra 등을 실행)을 사용하고 백본에서 사용할 수있는 클라이언트 측 템플릿과 Jasmine JavaScript 사양을 작성하는 것입니다. 이것은 서버 측과 클라이언트 측 사이의 마크 업 중복을 차단합니다.

거기에서 서버가 렌더링 한 HTML과 JavaScript가 작동하도록하려면 몇 가지 추가 단계를 수행해야합니다. 전달 된 시맨틱 마크 업을 가져 와서 JavaScript로 향상시킵니다.

예를 들어,로 이미지 갤러리 응용 프로그램을 구축하고 pushState있습니다. /images/1서버에서 요청 하면 서버에서 전체 이미지 갤러리를 렌더링하고 모든 HTML, CSS 및 JavaScript를 브라우저로 보냅니다. JavaScript를 비활성화하면 완벽하게 작동합니다. 모든 조치는 서버와 다른 URL을 요청하고 서버는 브라우저의 모든 마크 업을 렌더링합니다. 그러나 JavaScript를 사용하도록 설정 한 경우 JavaScript는 서버에서 생성 된 몇 가지 변수와 함께 이미 렌더링 된 HTML을 가져 와서 그 위치에서 가져옵니다.

예를 들면 다음과 같습니다.

<form id="foo">
  Name: <input id="name"><button id="say">Say My Name!</button>
</form>

서버가 이것을 렌더링 한 후, JavaScript는 그것을 선택합니다 (이 예제에서는 Backbone.js 뷰를 사용합니다)

FooView = Backbone.View.extend({
  events: {
    "change #name": "setName",
    "click #say": "sayName"
  },

  setName: function(e){
    var name = $(e.currentTarget).val();
    this.model.set({name: name});
  },

  sayName: function(e){
    e.preventDefault();
    var name = this.model.get("name");
    alert("Hello " + name);
  },

  render: function(){
    // do some rendering here, for when this is just running JavaScript
  }
});

$(function(){
  var model = new MyModel();
  var view = new FooView({
    model: model,
    el: $("#foo")
  });
});

이것은 매우 간단한 예이지만, 나는 그것이 요점을 얻는다고 생각합니다.

페이지가로드 된 후보기를 인스턴스화하면 서버에서 렌더링 한 양식의 기존 내용을보기 인스턴스로보기 인스턴스에 el제공합니다. 나는 하지 렌더링 호출 또는 뷰가 발생 가진 el첫 번째보기를로드 할 때, 나를 위해. 뷰가 실행되고 페이지가 모두 JavaScript 인 후에 사용할 수있는 렌더링 방법이 있습니다. 필요한 경우 나중에 뷰를 다시 렌더링 할 수 있습니다.

JavaScript가 활성화 된 상태에서 “내 이름 말하기”버튼을 클릭하면 경고 상자가 나타납니다. JavaScript가 없으면 서버에 다시 게시되고 서버는 이름을 html 요소에 어딘가에 렌더링 할 수 있습니다.

편집하다

첨부해야 할 목록이있는 더 복잡한 예를 고려하십시오 (아래 주석에서)

<ul>태그 에 사용자 목록이 있다고 가정하십시오 . 이 목록은 브라우저가 요청했을 때 서버에서 렌더링되었으며 결과는 다음과 같습니다.

<ul id="user-list">
  <li data-id="1">Bob
  <li data-id="2">Mary
  <li data-id="3">Frank
  <li data-id="4">Jane
</ul>

이제이 목록을 반복하고 각 <li>항목에 백본보기 및 모델을 연결해야 합니다. 이 data-id속성을 사용하면 각 태그의 모델을 쉽게 찾을 수 있습니다. 그런 다음이 HTML에 자신을 첨부 할만큼 똑똑한 컬렉션보기 및 항목보기가 필요합니다.

UserListView = Backbone.View.extend({
  attach: function(){
    this.el = $("#user-list");
    this.$("li").each(function(index){
      var userEl = $(this);
      var id = userEl.attr("data-id");
      var user = this.collection.get(id);
      new UserView({
        model: user,
        el: userEl
      });
    });
  }
});

UserView = Backbone.View.extend({
  initialize: function(){
    this.model.bind("change:name", this.updateName, this);
  },

  updateName: function(model, val){
    this.el.text(val);
  }
});

var userData = {...};
var userList = new UserCollection(userData);
var userListView = new UserListView({collection: userList});
userListView.attach();

이 예제에서는 UserListView모든 <li>태그를 반복하고 각 태그에 대해 올바른 모델로보기 객체를 연결합니다. 모델의 이름 변경 이벤트에 대한 이벤트 핸들러를 설정하고 변경이 발생할 때 요소의 표시된 텍스트를 업데이트합니다.


서버가 렌더링 한 html을 가져 와서 내 JavaScript로 가져 와서 실행하는 이런 종류의 프로세스는 SEO, 접근성 및 pushState지원을 위해 롤링 할 수있는 좋은 방법 입니다.

희망이 도움이됩니다.


답변

나는 이것이 당신이 필요하다고 생각합니다 : http://code.google.com/web/ajaxcrawling/

서버에서 자바 스크립트를 실행하여 페이지를 “렌더링”하는 특수 백엔드를 설치 한 다음 Google에 제공 할 수도 있습니다.

두 가지를 결합하면 두 가지를 프로그래밍하지 않고도 솔루션을 얻을 수 있습니다. (앵커 조각을 통해 앱을 완전히 제어 할 수있는 한)


답변

따라서 주요 관심사는 DRY 인 것 같습니다.

  • pushState를 사용하는 경우 서버에서 이미지 등을 제공하기위한 파일 확장자가없는 모든 URL에 대해 동일한 정확한 코드를 보내도록합니다. “/ mydir / myfile”, “/ myotherdir / myotherfile”또는 root “/ “-모든 요청은 동일한 정확한 코드를받습니다. 일종의 URL 재 작성 엔진이 필요합니다. 또한 약간의 html을 제공 할 수 있고 나머지는 CDN에서 나올 수 있습니다 (recess.js를 사용하여 종속성 관리-https://stackoverflow.com/a/13813102/1595913 참조 ).
  • (링크를 URL 체계로 변환하고 정적 또는 동적 소스를 쿼리하여 컨텐츠 존재 여부를 테스트하여 링크의 유효성을 테스트하십시오. 유효하지 않은 경우 404 응답을 보내십시오.)
  • 요청이 Google 봇이 아닌 경우 정상적으로 처리합니다.
  • Google 봇에서 요청한 경우 헤드리스 웹킷 브라우저 ( “헤드리스 브라우저는 시각적 인터페이스가없는 완전한 기능을 갖춘 웹 브라우저입니다.” ) phantom.js를 사용 하여 서버에서 HTML 및 자바 스크립트를 렌더링하고 구글은 결과 HTML을 봇. 봇이 html을 구문 분석 할 때 서버의 다른 “pushState”링크 / somepage에 도달 할 수 있으며, 서버 <a href="https://stackoverflow.com/someotherpage">mylink</a>는 URL을 애플리케이션 파일에 다시 쓰고 phantom.js에 로드하고 결과 HTML을 봇에 전송합니다. ..
  • 귀하의 HTML에 대해서는 일종의 하이재킹과 함께 일반 링크를 사용한다고 가정합니다 (예 : backbone.js https://stackoverflow.com/a/9331734/1595913 )
  • 링크와 혼동을 피하기 위해 ason.mysite.com과 같이 json을 제공하는 api 코드를 별도의 하위 도메인으로 분리하십시오.
  • 성능을 향상시키기 위해 phantom.js와 동일한 메커니즘을 사용하여 정적 버전의 페이지를 작성하고 결과적으로 Google 봇에 정적 페이지를 제공하여 휴무 시간 동안 검색 엔진에 대한 사이트 페이지를 사전 처리 할 수 ​​있습니다. <a>태그 를 구문 분석 할 수있는 간단한 앱으로 사전 처리를 수행 할 수 있습니다 . 이 경우 URL 경로가 포함 된 이름의 정적 파일이 있는지 간단히 확인할 수 있으므로 404 처리가 더 쉽습니다.
  • #을 사용하면! 사이트에 대한 해시 뱅 구문은 URL 재 작성 URL 서버 엔진이 URL에서 _escaped_fragment_를 찾고 URL을 URL 체계로 형식화한다는 점을 제외하고 유사한 시나리오를 적용합니다.
  • github에는 node.js와 phantom.js가 통합되어 있으며 node.js를 웹 서버로 사용하여 html 출력을 생성 할 수 있습니다.

다음은 seo에 phantom.js를 사용하는 몇 가지 예입니다.

http://backbonetutorials.com/seo-for-single-page-apps/

http://thedigitalself.com/blog/seo-and-javascript-with-phantomjs-server-side-rendering


답변

Rails를 사용하는 경우 poirot을 사용해보십시오 . 콧수염 이나 핸들 바 템플릿을 클라이언트와 서버 측 에서 재사용하는 것이 간단합니다 .

와 같은보기에서 파일을 작성하십시오 _some_thingy.html.mustache.

렌더 서버 측 :

<%= render :partial => 'some_thingy', object: my_model %>

클라이언트 측 사용을 위해 템플릿을 머리에 넣으십시오.

<%= template_include_tag 'some_thingy' %>

Rendre 고객 측 :

html = poirot.someThingy(my_model)


답변

약간 다른 각도를 취하기 위해 두 번째 솔루션은 접근성 측면에서 올바른 솔루션입니다 … 자바 스크립트를 사용할 수없는 사용자 (스크린 리더 등)에게 대체 콘텐츠를 제공 할 것입니다.

이것은 자동으로 SEO의 이점을 추가 할 것이며 내 생각으로는 Google의 ‘못된’기술로 보지 않을 것입니다.


답변

흥미 롭군 가능한 솔루션을 찾고 있었지만 꽤 문제가있는 것 같습니다.

나는 실제로 두 번째 접근 방식에 더 기울고있었습니다.

서버가 검색 엔진 봇 전용 웹 사이트를 제공하도록합니다. 일반 사용자가 http://example.com/my_path를 방문 하면 서버는 웹 사이트의 JavaScript 무거운 버전을 제공해야합니다. 그러나 Google 봇이 방문하는 경우 서버는 Google에서 색인을 생성하려는 콘텐츠가 포함 된 최소한의 HTML을 제공해야합니다.

다음은 문제 해결에 대한 설명입니다. 작동하지는 않지만 다른 개발자에게 통찰력이나 아이디어를 제공 할 수 있습니다.

“푸시 상태”기능을 지원하는 JS 프레임 워크를 사용하고 백엔드 프레임 워크가 Ruby on Rails라고 가정하십시오. 간단한 블로그 사이트가 있으며 검색 엔진을 사용하여 모든 기사 indexshow페이지 를 색인화하려고 합니다.

경로를 다음과 같이 설정했다고 가정 해 보겠습니다.

resources :articles
match "*path", "main#index"

모든 서버 측 컨트롤러가 클라이언트 측 프레임 워크를 실행하는 데 필요한 동일한 템플릿 (html / css / javascript / etc)을 렌더링하는지 확인하십시오. 요청에서 일치하는 컨트롤러가없는 경우 (이 예에서는에 대한 RESTful 조치 세트 만 있음 ArticlesController) 다른 항목 만 일치시키고 템플리트를 렌더링하고 클라이언트 측 프레임 워크가 라우팅을 처리하도록하십시오. 컨트롤러와 와일드 카드 매처를 맞추는 것의 유일한 차이점은 JavaScript를 사용하지 않는 장치에 요청한 URL을 기반으로 컨텐츠를 렌더링하는 기능입니다.

내가 이해 한 바에 따르면 브라우저에 보이지 않는 내용을 렌더링하는 것은 좋지 않습니다. Google이 색인을 생성하면 사람들이 Google을 통해 특정 페이지를 방문하고 콘텐츠가없는 경우 벌칙을 받게됩니다. 염두에 두어야 divdisplay: none것은 CSS 의 노드 에서 내용을 렌더링 한다는 것 입니다.

그러나 단순히 이렇게하면 문제가되지 않습니다.

<div id="no-js">
  <h1><%= @article.title %></h1>
  <p><%= @article.description %></p>
  <p><%= @article.content %></p>
</div>

그런 다음 JavaScript를 사용하는 장치가 페이지를 열 때 실행되지 않는 JavaScript를 사용하십시오.

$("#no-js").remove() # jQuery

이렇게하면 Google과 JavaScript를 사용하지 않는 기기를 사용하는 모든 사람에게 원시 / 정적 콘텐츠가 표시됩니다. 따라서 내용 물리적으로 존재하며 JavaScript를 사용할 수없는 장치가있는 모든 사람이 볼 수 있습니다.

실제로 사용자가 방문하는 동일한 페이지와이 때, 자바 스크립트를 사용할 수의 #no-js노드는 응용 프로그램을 아무런 영향도 미치지 않도록 제거됩니다. 그런 다음 클라이언트 측 프레임 워크는 라우터를 통해 요청을 처리하고 JavaScript가 활성화되면 사용자에게 표시되는 내용을 표시합니다.

나는 이것이 유효하고 상당히 쉬운 기술이라고 생각합니다. 웹 사이트 / 애플리케이션의 복잡성에 따라 달라질 수 있습니다.

그러나 그렇지 않은 경우 수정하십시오. 그냥 내 생각을 공유 할 줄 ​​알았는데


답변

서버 측에서 NodeJS를 사용하고, 클라이언트 측 코드를 브라우저하고, 서버 측 클라이언트를 통해 각 http- 요청 (정적 http 자원 제외)을 라우팅하여 첫 번째 ‘부트 스냅'(페이지 상태의 스냅 샷)을 제공하십시오. 서버에서 jquery dom-ops를 처리하려면 jsdom과 같은 것을 사용하십시오. 부트 스냅이 반환 된 후 웹 소켓 연결을 설정하십시오. 클라이언트 측에서 일종의 래퍼 연결을 만들어 웹 소켓 클라이언트와 서버 측 클라이언트를 구별하는 것이 가장 좋습니다 (서버 측 클라이언트는 서버와 직접 통신 할 수 있음). 나는 다음과 같은 일을 해왔다 : https://github.com/jvanveen/rnet/