"단일 페이지"JS 웹 사이트 및 SEO


128

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

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

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

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

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

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

답변:


44

# 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지원을 위해 롤링 할 수있는 좋은 방법 입니다.

희망이 도움이됩니다.


나는 당신의 요점을 얻었지만 흥미로운 것은 "자바 스크립트가 인계 된"후에 렌더링이 어떻게 수행되는지입니다. 보다 복잡한 예에서는 클라이언트에서 컴파일되지 않은 템플릿을 사용하여 사용자 배열을 반복하여 목록을 작성해야 할 수 있습니다. 사용자의 모델이 변경 될 때마다보기가 다시 렌더링됩니다. 템플릿을 복제하지 않고 서버가 클라이언트에 대한 뷰를 렌더링하도록 요청하지 않고 어떻게 할 수 있습니까?
user544941

내가 링크 한 2 개의 블로그 게시물은 클라이언트와 서버에서 사용할 수있는 템플릿을 복제하는 방법을 종합적으로 보여줍니다. 서버가 액세스 가능하고 SEO에 친숙해 지려면 전체 페이지를 렌더링해야합니다. 서버에서 렌더링 한 사용자 목록에 첨부하는보다 복잡한 예를 포함하도록 답변을 업데이트했습니다.
Derick Bailey

22

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

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

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


사실, 내가 찾고있는 것이 아닙니다. 그것들은 첫 번째 솔루션의 변형이며 내가 언급했듯이 그 접근 방식에별로 만족하지 않습니다.
user544941

2
당신은 내 전체 답변을 읽지 않았습니다. 또한 자바 스크립트를 렌더링하는 특수 백엔드를 사용합니다. 두 번 쓰지 않습니다.
Ariel

예, 읽었습니다. 그러나 내가 당신을 올바르게 이해 시키면 프로그램의 지옥 일 것입니다. pushState를 트리거하는 모든 동작을 시뮬레이트해야하기 때문입니다. 또는 직접 조치를 취할 수는 있지만 더 이상 건조하지 않습니다.
user544941

2
나는 그것이 기본적으로 프론트가없는 브라우저라고 생각합니다. 그러나 앵커 프래그먼트에서 프로그램을 완전히 제어 할 수 있어야합니다. 또한 모든 링크에 onClicks와 함께 또는 onClicks 대신 적절한 조각이 있는지 확인해야합니다.
Ariel

17

따라서 주요 관심사는 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


4

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

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

렌더 서버 측 :

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

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

<%= template_include_tag 'some_thingy' %>

Rendre 고객 측 :

html = poirot.someThingy(my_model)

3

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

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


그리고 아무도 당신을 잘못 증명 했습니까? 댓글이 게시 된 지 얼마되지 않았습니다
jkulak

1

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

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

서버가 검색 엔진 봇 전용 웹 사이트를 제공하도록합니다. 일반 사용자가 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가 활성화되면 사용자에게 표시되는 내용을 표시합니다.

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

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


1
글쎄, 당신이 처음에 내용을 표시하고 조금 후에 그것을 제거하면, 아마도 최종 사용자는 아마도 브라우저에서 내용이 깜박이거나 깜박임을 알 수 있습니다 :) 특히 느린 브라우저라면, HTML 컨텐츠의 거대한 크기는 당신이 표시 / 제거하려고 시도하고 일부는 JS 코드가로드되고 실행되기 전에 지연됩니다. 당신은 어떻게 생각하세요?
Evereq

1

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


0

Google 클로저 템플릿 을 사용 하여 페이지를 렌더링 하십시오 . javascript 또는 java로 컴파일되므로 클라이언트 또는 서버 측에서 페이지를 쉽게 렌더링 할 수 있습니다. 모든 클라이언트와의 첫 만남에서 HTML을 렌더링하고 헤더에 링크로 javascript를 추가하십시오. 크롤러는 html 만 읽지 만 브라우저는 스크립트를 실행합니다. 트래픽을 최소화하기 위해 브라우저의 모든 후속 요청을 API에 대해 수행 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.