https://www.endpointdev.com/blog/tags/seo/2021-04-30T00:00:00+00:00End Point Dev3 Immediate Benefits of Google Analytics for Business Ownershttps://www.endpointdev.com/blog/2021/04/benefits-of-google-anltcs-for-business/2021-04-30T00:00:00+00:00Ben Witten
<p><img src="/blog/2021/04/benefits-of-google-anltcs-for-business/banner.png" alt="">
Image from <a href="https://blog.google/products/marketingplatform/analytics/new_google_analytics/">Google’s marketing platform blog</a></p>
<p>Where is your traffic coming from? What drew the traffic to your website? Which parts of your website are most visited? How do visits change over time? And how can the answers to these questions help you?</p>
<p>Answering such questions and doing something about it is called search engine optimization (SEO).</p>
<p>To help you with this is Google Analytics, a web analytics service that lets you track and understand your website traffic. It is a valuable tool for businesses of all sizes that are looking to grow.</p>
<p>Here are three ways Google Analytics can benefit your business:</p>
<h3 id="determining-site-improvements-to-strengthen-website-flow">Determining Site Improvements to Strengthen Website Flow</h3>
<p>This is a great way to generate more “conversions” — visitors to your website taking a desired action. Are visitors behaving the way you expected them to? Can you observe any bottlenecks in audience flow?</p>
<p>Bottlenecks include traffic getting stuck on one page, when you want them to be going to a different one, like a contact page. Understanding how traffic gets stuck might point you toward the need to refresh certain web pages, which could in turn lead to more conversions.</p>
<p>For example, we observed that our “Deployment Automation” Expertise subpage has had a 100% bounce rate over the past three months. This is concerning because it means that the content may not be engaging or there may not be a clear visitor navigation path, the end goal being a contact submission. Analytics helped us start looking at how to strengthen this subpage.</p>
<p><img src="/blog/2021/04/benefits-of-google-anltcs-for-business/image-1.jpg" alt="">
Image from <a href="https://blog.google/products/marketingplatform/analytics/new_google_analytics/">Google’s marketing platform blog</a></p>
<h3 id="understanding-your-audience">Understanding your Audience</h3>
<p>Who is coming to your site, and how are they finding you? What referral sites, partner sites, media, and blog posts are directing the most traffic to your page? How can you leverage that?</p>
<p><img src="/blog/2021/04/benefits-of-google-anltcs-for-business/image-2.png" alt=""></p>
<p>In reviewing your inbound traffic, you will see some combination of the following types of traffic:</p>
<ul>
<li>Direct: Traffic from directly typing the URL into the browser address bar.</li>
<li>Organic: Traffic from people who navigate to your website through search engines after seeing you in search results. Having a strong online presence, especially strong SEO, will help more visitors arrive on your website without the need to pay for them.</li>
<li>Referral: Traffic that comes to your website after being “referred” from a different website. This is when other websites link to your webpage. More backlinks and referral traffic typically leads to significant SEO benefits.</li>
<li>Paid: This traffic arrives from paid search campaigns on platforms such as Google Ads.</li>
<li>Email: Traffic from links in emails.</li>
<li>Social: Traffic that comes from posts on social media networks like Facebook, LinkedIn, and Twitter.</li>
<li>Other: All the traffic which doesn’t fit in any other category.</li>
</ul>
<p>We recommend reviewing each type of traffic to get a better understanding of their flow through your website, and noting any trends you find within individual web traffic sources and mediums.</p>
<p><img src="/blog/2021/04/benefits-of-google-anltcs-for-business/image-3.png" alt=""></p>
<h3 id="data-driven-decision-making-stop-relying-on-assumptions-and-rely-on-data">Data-Driven Decision Making: Stop Relying on Assumptions and Rely on Data</h3>
<p>One great challenge to businesses is overconfidence in how much you understand about your audience. Google Analytics, and other similar analytics tools, can transform your work culture from being based on opinions and assumptions to being based on hard data. Google Analytics provides data in an organized and impactful format, and using analytics data in tandem with sales efforts can lead to more conversions and revenue for your business.</p>
<h3 id="alternatives">Alternatives</h3>
<p>With Google having access to so much data and being one of the two major advertisers on the web, many people are looking for alternatives that allow them more control over their customer data, separation from Google’s advertising platforms, and a slimmer data footprint for compliance with privacy laws such as CCPA (California) and GDPR (European Union).</p>
<p>There have always been various options for web visitor analytics. Google Analytics was originally created by a company called Urchin Software, which Google acquired in 2005. Some current alternatives include:</p>
<ul>
<li><a href="https://www.cloudflare.com/web-analytics/">Cloudflare web analytics</a>, a new service offered by the popular CDN (Content Distribution Network) that simply shows visitor data already flowing through their systems.</li>
<li><a href="https://www.goatcounter.com/">GoatCounter</a>, a SaaS or self-hosted open source application, which aims to provide simple counters rather than collecting personal data, thus avoiding any need for a privacy notice.</li>
<li><a href="https://matomo.org/">Matomo</a>, formerly known as Piwik, a fully-featured SaaS or on-premises paid package with a limited open source version.</li>
<li><a href="http://www.openwebanalytics.com/">Open Web Analytics</a>, a customizable open source analytics framework.</li>
</ul>
<p>We at End Point have found success with these core ideas and several of these services. We are happy to provide a <a href="/contact/">free consultation</a> to discuss your website needs.</p>
Web Projects for a Rainy Dayhttps://www.endpointdev.com/blog/2020/03/web-projects-for-rainy-day/2020-03-25T00:00:00+00:00Elizabeth Garrett Christensen
<p><img src="/blog/2020/03/web-projects-for-rainy-day/image-0.jpg" alt="raindrops on a plant"></p>
<p><a href="https://www.flickr.com/photos/yellowstonenps/32984582893/">Image</a> by <a href="https://www.flickr.com/photos/yellowstonenps/">Yellowstone NPS on Flickr</a></p>
<p>With the COVID-19 quarantine disrupting life for many of us, I thought I’d put together a list of things you can do with your website on a rainy day. These are things to keep your business moving even if you’re at home and some of your projects are stuck waiting on things to reopen. If you’re looking for some useful things to do to fill your days over the next few months, this post is for you!</p>
<h3 id="major-version-updates">Major Version Updates</h3>
<p>Make a list of your entire stack, from OS to database to development frameworks. Note the current version and research the current supported versions. I find Wikipedia pages to be fairly reliable for this (e.g. <a href="https://en.wikipedia.org/wiki/CentOS">en.wikipedia.org/wiki/CentOS</a>). Ok, so what things need to be updated, or will need to be in the next year? Start on those now and use some downtime to get ahead of your updates.</p>
<h4 id="sample-of-a-clients-stack-review">Sample of a client’s stack review</h4>
<div class="table-scroll">
<table>
<thead>
<td>Software</td>
<td>Purpose</td>
<td>Our version</td>
<td>Release date</td>
<td>End of support</td>
<td>Next update</td>
<td>Newest version</td>
<td>Notes</td>
</thead>
<tr>
<td>CentOS</td>
<td>OS for e-commerce server</td>
<td>7</td>
<td>July 2014</td>
<td>June 2024</td>
<td>Not imminent</td>
<td>8</td>
<td><a href="https://wiki.centos.org/About/Product">https://wiki.centos.org/About/Product</a></td>
</tr>
<tr>
<td>Nginx</td>
<td>Web server</td>
<td>1.16.0</td>
<td>March 2020</td>
<td>Unclear</td>
<td>Not imminent</td>
<td>1.16.1</td>
<td><a href="https://nginx.org/">https://nginx.org/</a></td>
</tr>
<tr>
<td>PostgreSQL</td>
<td>Database server</td>
<td>9.5.20</td>
<td>January 2016</td>
<td>Feb 2020</td>
<td>Medium term, to version 11</td>
<td>12</td>
<td><a href="https://www.postgresql.org/support/versioning/">https://www.postgresql.org/support/versioning/</a></td>
</tr>
<tr>
<td>Rails</td>
<td>App framework for store</td>
<td>5.1</td>
<td>February 2017</td>
<td>Current</td>
<td>Long Term, to version 6</td>
<td>6</td>
<td><a href="https://rubygems.org/gems/rails/versions>https://rubygems.org/gems/rails/versions</a></td>
</tr>
<tr>
<td>Spree</td>
<td>Ecommerce and admin gem</td>
<td>3.3</td>
<td>April 2017</td>
<td>Current</td>
<td>Long Term, to version 4</td>
<td>4</td>
<td><a href="https://rubygems.org/gems/spree/versions">https://rubygems.org/gems/spree/versions</a></td>
</tr>
<tr>
<td>Elasticsearch</td>
<td>Search platform for product import/search</td>
<td>5.6.x</td>
<td>September 2017</td>
<td>March 2019</td>
<td>Immediate, to version 6.8</td>
<td>7.4</td>
<td><a href="https://www.elastic.co/support/eol">https://www.elastic.co/support/eol</a></td>
</tr>
<tr>
<td>WordPress</td>
<td>Info site</td>
<td>5.2.3</td>
<td>September 2019</td>
<td>Unclear</td>
<td>5.2.4 shipped recently</td>
<td>5.2</td>
<td><a href="https://codex.wordpress.org/Supported_Versions">https://codex.wordpress.org/Supported_Versions</a></td>
</tr>
</table>
</div>
<h3 id="content-cleanup--seo-review">Content Cleanup & SEO Review</h3>
<p>Everyone’s website gets cluttered with outdated content. Take a look at your pages, review, and update what needs to be changed. Pay attention to search engine optimization (SEO) concerns as you go through it. Make sure your content has headers, accurate keywords, and good meta-descriptions. Research SEO best practices if you need a refresher.</p>
<p>Nowadays, reducing repeated content has huge benefits for SEO so we recommend any content review includes a review of duplication. If you have a small site, you can go through your content and SEO manually. Larger projects can utilize tools on the market such as <a href="https://www.siteliner.com/">Siteliner</a> or <a href="https://wordpress.org/plugins/wp-optimize/">WPOptimize</a>.</p>
<p>While you’re taking a dive into content, don’t forget to review your Google Analytics and understand what content is being used and what isn’t. Google has added many new features to Analytics and Ads, so it’s a good idea to refresh yourself on the updated documentation and new features.</p>
<h3 id="reporting">Reporting</h3>
<p>A lot of clients with big ecommerce data sets or other applications that collect data benefit from a separate reporting or business analytics tool. A rainy day can be a good time to think about what reports you want on last year’s business, what data will help you plan for the future. End Point has worked with a few different reporting tools that easily add on to your database, like <a href="https://www.hitachivantara.com/en-us/products/data-management-analytics/pentaho.html">Pentaho</a> or <a href="https://www.jaspersoft.com/reporting-software">Jasper</a> and those can be really useful.</p>
<h3 id="documentation">Documentation</h3>
<p>I wouldn’t be a good project manager if I didn’t throw this one in the list. Documentation is so, so important, yet really we can always do more. End Point uses a few different tools, including wikis running <a href="https://www.mediawiki.org/wiki/MediaWiki">MediaWiki</a> and Google Docs, for keeping track of project details. Now’s a good time to set up a nice documentation system or do a big review and make sure everything is updated and back in order. Maybe dream of a vacation you <em>might</em> be able to take when this is over and make sure everything’s ready for you to do that.</p>
<h3 id="disaster-recovery-tests">Disaster Recovery Tests</h3>
<p>For anyone with business-critical infrastructure, you need to ensure you know how to get everything back up and running in the case of a major failure, either with on-premises or cloud hosting. Now’s a good time to clarify with your hosting vendor things like: What are your backups like? What is your disaster recovery plan? What is the timeline for recovering the application in the event of a major failure? If you can, take time to do a simulation and make sure all the pieces are there if they need to be. Simply said, we also need to test our backups in order to ensure that they actually work.</p>
<h3 id="redesign">Redesign</h3>
<p>If you’ve been meaning to refresh your website, a rainy day is prime time to do it. Designers and developers are looking for projects and you’ll have extra time on your hands to oversee the process, spend time reviewing and testing, and get things done just the way you want.</p>
<h3 id="automated-testing">Automated Testing</h3>
<p>Good developers want an automated test suite as part of their application. Not all applications were built with this from the beginning and many didn’t have the budget or time to get it done. With extra time on your hands, this can be a great time to start building your test suite or to improve the coverage of your existing one.</p>
<p>Unit tests in particular are a good place to start. Unit tests are great not only because they help validate software correctness and protect against regressions, but also because they require a well-factored and modular system. This means that, while writing your unit tests, you will often be forced to go back to your application’s code base and refactor it to make it testable, to make it better. Investing in creating a solid unit test suite is a great bang for your buck. You can also look at implementing continuous integration—having a pipeline to let multiple developers deploy code throughout the day and configure your automated tests into the workflow.</p>
<h3 id="versioning--deployment-tools">Versioning & Deployment Tools</h3>
<p>When you’re cleaning house, take a look at your Git version control repository and make sure everything important is in there. We have a few projects that have a main project in Git, but sometimes the smaller projects and one-offs can go astray. This is a good time to get everything organized into one repository, or make sure external repositories are connected and integrated.</p>
<p>Automated DevOps deployment tools can also be nice to work on. Tools like Ansible and Chef can take a lot of time to set up and test, but they have some great time-saving and accuracy advantages down the line. Our in-house security experts also recommend tools like AIDE and OSSEC which automate monitoring file changes daily.</p>
<h3 id="security-audit-and-monitoring">Security Audit and Monitoring</h3>
<p>Taking a few days to review your personal security and that of your application is something you should do regularly and now’s a good time to plan for it. Charlie’s got <a href="/blog/2020/02/end-point-security-tips/">a great security post</a> that’s a good top-level review. For application security, End Point uses some tools for vulnerability scanning. We also have a checklist of basic security items that include password handling, PII data, and other common security holes. For certain projects/clients we must also take HIPAA or PCI DSS compliance into account. Also, don’t neglect to review your TLS status and ensure that web applications run on TLS 1.2 and are TLS 1.3 ready. This also may relate to the underlying operating systems—whether they are able to support the latest TLS version natively.</p>
<h3 id="optimization-and-performance">Optimization and Performance:</h3>
<p>Most of the time new features have higher priority than improving the performance of an existing system. It could be the right time to review core functionalities and list out the areas that need improvement in serving a better experience to customers by optimization. The areas can be focused on optimizing code, database queries, image size, data compression over network, adding cache, CDN, and so on. We’ve been moving quite a few clients to the <a href="https://www.cloudflare.com/">Cloudflare</a> DNS and CDN service and we’ve been really happy with it. Optimization work will definitely influence the customer retention rate which helps to increase profitability long term.</p>
<h3 id="refactoring">Refactoring</h3>
<p>Along the same lines as optimization, code refactoring can have long term gains in performance and ease of future development. Think of this like house cleaning. It is always easier to find any item in the house when things are arranged in an orderly manner. Similarly, the organized and clean code base will play a vital role in future code changes and development, helping to reduce the chances of unexpected bugs, save time making changes at one place and improving code readability. Disciplined refactoring delivers readable, reusable, non-redundant code. Refactoring can be applied to your databases and user interfaces as well.</p>
<p>Want to get started on some background projects for your website? <a href="/contact/">Talk to us today</a>.</p>
Decreasing your website load timehttps://www.endpointdev.com/blog/2020/01/decreasing-website-load-time/2020-01-07T00:00:00+00:00Juan Pablo Ventoso
<p><img src="/blog/2020/01/decreasing-website-load-time/mobile-desktop-browsing.jpg" alt="Decreasing our website load time" /> <a href="https://www.flickr.com/photos/johanl/6798184016/">Photo</a> by <a href="https://www.flickr.com/photos/johanl/">Johan Larsson</a>, used under <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a></p>
<p>We live in a competitive world, and the web is no different. Improving latency issues is crucial to any Search Engine Optimization (SEO) strategy, increasing the website’s ranking and organic traffic (visitors from search engines) as a result.</p>
<p>There are many factors that can lead to a faster response time, including optimization of your hosting plan, server proximity to your main traffic source, or utilization of a Content Distribution Network (CDN) if you are expecting visitors on an international level. Some of these solutions and many others can be implemented with only a couple hours of coding.</p>
<h3 id="inline-styles-and-scripts-for-the-topmost-content">Inline styles and scripts for the topmost content</h3>
<p>Nobody enjoys waiting for long load times. When opening a Google search link, being met with a blank page or a loading GIF for several seconds can seem agonizing. That’s why optimizing the initial rendering of your page is crucial.</p>
<p>The content that immediately appears to the user without the need to scroll down is referred to as “above-the-fold”. This is where your optimization efforts should be aimed. So here’s a plan to load and display as quickly as possible:</p>
<ul>
<li>
<p>First, differentiate the critical styles and scripts you need to render the topmost content, and separate them from the rest of our stylesheet and external script references.</p>
</li>
<li>
<p>Then, <a href="https://www.imperva.com/learn/performance/minification/">minify</a> the separated <a href="https://csscompressor.com/">styles</a> and <a href="https://jscompress.com/">scripts</a>, and insert them directly on our page template, right before the closing <code></head></code> tag.</p>
</li>
<li>
<p>Finally, take the stylesheet and scripts link references from the <code><head></code> tag (where it’s usually located) and move them to the end of the above-the-fold content.</p>
</li>
</ul>
<p>Now, the user won’t have to wait until all references are loaded before seeing content. <b>Tip</b>: Remember to use the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-async">async</a> tag on scripts whenever possible.</p>
<p><strong>example.html:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">head</span>>
<<span style="color:#b06;font-weight:bold">style</span>>{<span style="color:#a61717;background-color:#e3d2d2">above-the-fold</span> <span style="color:#a61717;background-color:#e3d2d2">minified</span> <span style="color:#a61717;background-color:#e3d2d2">inline</span> <span style="color:#a61717;background-color:#e3d2d2">styles</span> <span style="color:#a61717;background-color:#e3d2d2">goes</span> <span style="color:#a61717;background-color:#e3d2d2">here</span>}</<span style="color:#b06;font-weight:bold">style</span>>
<<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">type</span>=<span style="color:#d20;background-color:#fff0f0">"text/javascript"</span>>{above-the-fold critical scripts goes here}</<span style="color:#b06;font-weight:bold">script</span>>
</<span style="color:#b06;font-weight:bold">head</span>>
<<span style="color:#b06;font-weight:bold">body</span>>
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"above-the-fold-content"</span>></<span style="color:#b06;font-weight:bold">div</span>>
<<span style="color:#b06;font-weight:bold">link</span> <span style="color:#369">rel</span>=<span style="color:#d20;background-color:#fff0f0">"stylesheet"</span> <span style="color:#369">href</span>=<span style="color:#d20;background-color:#fff0f0">"{below-the-fold minified stylesheet reference goes here}"</span> />
<<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">async</span> <span style="color:#369">src</span>=<span style="color:#d20;background-color:#fff0f0">"{below-the-fold minified javascript reference goes here}"</span> />
<<span style="color:#b06;font-weight:bold">div</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"below-the-fold-content"</span>></<span style="color:#b06;font-weight:bold">div</span>>
</<span style="color:#b06;font-weight:bold">body</span>>
</code></pre></div><h3 id="deferred-loading-of-ads">Deferred loading of ads</h3>
<p>If you’re monetizing your website through Google AdSense or another ad agency that uses scripts to load ads, consider loading ads after the content is fully rendered. This may have a small impact on your revenue, but will improve the user’s experience while optimizing the load speed.</p>
<p>Although there are several ways to achieve this, a technique I have successfully used on many websites is removing all of the script references to Google AdSense until your page is fully loaded. A short delay can be added in order to allow some browsing time before showing ads.</p>
<p>Remove script references, the comment, and extra spaces from your original ad code, to convert it from something like this…</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">script</span> <span style="color:#369">async</span> <span style="color:#369">src</span>=<span style="color:#d20;background-color:#fff0f0">"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"</span>></<span style="color:#b06;font-weight:bold">script</span>>
<span style="color:#888"><!-- Your ad name --></span>
<<span style="color:#b06;font-weight:bold">ins</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"adsbygoogle"</span>
<span style="color:#369">style</span>=<span style="color:#d20;background-color:#fff0f0">"display:inline-block;width:728px;height:90px"</span>
<span style="color:#369">data-ad-client</span>=<span style="color:#d20;background-color:#fff0f0">"ca-pub-XXXXXXXXXXXXXXXXX"</span>
<span style="color:#369">data-ad-slot</span>=<span style="color:#d20;background-color:#fff0f0">"XXXXXXXXX"</span>></<span style="color:#b06;font-weight:bold">ins</span>>
<<span style="color:#b06;font-weight:bold">script</span>>
(adsbygoogle = <span style="color:#038">window</span>.adsbygoogle || []).push({});
</<span style="color:#b06;font-weight:bold">script</span>>
</code></pre></div><p>… to something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">ins</span> <span style="color:#369">class</span>=<span style="color:#d20;background-color:#fff0f0">"adsbygoogle"</span> <span style="color:#369">style</span>=<span style="color:#d20;background-color:#fff0f0">"display:inline-block;width:728px;height:90px"</span> <span style="color:#369">data-ad-client</span>=<span style="color:#d20;background-color:#fff0f0">"ca-pub-XXXXXXXXXXXXXXXXX"</span> <span style="color:#369">data-ad-slot</span>=<span style="color:#d20;background-color:#fff0f0">"XXXXXXXXX"</span>></<span style="color:#b06;font-weight:bold">ins</span>>
</code></pre></div><p>A lot shorter, isn’t it? This will create an empty slot in which the ad will be displayed after the page is fully rendered. To accomplish that, a new script like the one below must be added (assuming jQuery is present on the website):</p>
<p><strong>async-ads.js:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript"><span style="color:#888">// Create a script reference
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">function</span> addScript(src, <span style="color:#080;font-weight:bold">async</span>, callback) {
<span style="color:#080;font-weight:bold">var</span> js = <span style="color:#038">document</span>.createElement(<span style="color:#d20;background-color:#fff0f0">"script"</span>);
js.type = <span style="color:#d20;background-color:#fff0f0">"text/javascript"</span>;
<span style="color:#080;font-weight:bold">if</span> (<span style="color:#080;font-weight:bold">async</span>)
js.<span style="color:#080;font-weight:bold">async</span> = <span style="color:#080;font-weight:bold">true</span>;
<span style="color:#080;font-weight:bold">if</span> (callback)
js.onload = callback;
js.src = src;
<span style="color:#038">document</span>.body.appendChild(js);
}
<span style="color:#888">// Called when document is ready
</span><span style="color:#888"></span>$(<span style="color:#038">document</span>).ready(<span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#888">// Wait for one second to ensure the user started browsing
</span><span style="color:#888"></span> setTimeout(<span style="color:#080;font-weight:bold">function</span>() {
(adsbygoogle = <span style="color:#038">window</span>.adsbygoogle || []);
$(<span style="color:#d20;background-color:#fff0f0">"ins.adsbygoogle"</span>).each(<span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#d20;background-color:#fff0f0">"<script>(adsbygoogle = window.adsbygoogle || []).push({})</script>"</span>).insertAfter($(<span style="color:#080;font-weight:bold">this</span>));
});
addScript(<span style="color:#d20;background-color:#fff0f0">"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"</span>, <span style="color:#080;font-weight:bold">true</span>);
}, <span style="color:#00d;font-weight:bold">1000</span>);
});
</code></pre></div><p>This code will wait for one second once the document is ready, and then leave instructions for Google to push a new ad for each slot. Finally, the AdSense external script will be loaded so that Google will read the instructions and start filling all the slots with ads.</p>
<p><b>Tip</b>: Enabling balancing from your AdSense dashboard may improve the average load speed as well as the user’s experience since ads will not be shown when the expected revenue is deprecable. And if you’re still on the fence about showing fewer ads, <a href="https://fatstacksblog.com/adsense-ad-balance-experiment/">try out an experiment</a> like I did. A balance of 50% worked well in my case, but the right balance will depend on your niche and website characteristics.</p>
<h3 id="lazy-load-for-images">Lazy load for images</h3>
<p>Because the user will most likely spend the majority of the visit reading above-the-fold content (and may even leave before scrolling at all), loading all images from content below-the-fold at first is impractical. Implementing a custom lazy-loading script (also referred to as deferred-loading or loading-on-scroll) for images can be an easy process. Even though changes to the backend would be likely, the concept of this approach is simple:</p>
<ul>
<li>
<p>Replacing the <code>src</code> attributes from all images that will have lazy loading with a custom attribute such as <code>data-src</code> (this part will probably require backend changes) and set a custom class for them, like <code>lazy</code>.</p>
</li>
<li>
<p>Creating a script that will copy the <code>data-src</code> content into the <code>src</code> attribute as we scroll through the page.</p>
</li>
</ul>
<p><strong>lazy-load.js:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-javascript" data-lang="javascript">;(<span style="color:#080;font-weight:bold">function</span>($) {
$.fn.lazy = <span style="color:#080;font-weight:bold">function</span>(threshold, callback) {
<span style="color:#080;font-weight:bold">var</span> $w = $(<span style="color:#038">window</span>),
th = threshold || <span style="color:#00d;font-weight:bold">0</span>,
attrib = <span style="color:#d20;background-color:#fff0f0">"data-src"</span>,
images = <span style="color:#080;font-weight:bold">this</span>,
loaded;
<span style="color:#080;font-weight:bold">this</span>.one(<span style="color:#d20;background-color:#fff0f0">"lazy"</span>, <span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#080;font-weight:bold">var</span> source = <span style="color:#080;font-weight:bold">this</span>.getAttribute(attrib);
source = source || <span style="color:#080;font-weight:bold">this</span>.getAttribute(<span style="color:#d20;background-color:#fff0f0">"data-src"</span>);
<span style="color:#080;font-weight:bold">if</span> (source) {
<span style="color:#080;font-weight:bold">this</span>.setAttribute(<span style="color:#d20;background-color:#fff0f0">"src"</span>, source);
<span style="color:#080;font-weight:bold">if</span> (<span style="color:#080;font-weight:bold">typeof</span> callback === <span style="color:#d20;background-color:#fff0f0">"function"</span>) callback.call(<span style="color:#080;font-weight:bold">this</span>);
}
});
<span style="color:#080;font-weight:bold">function</span> lazy() {
<span style="color:#080;font-weight:bold">var</span> inview = images.filter(<span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#080;font-weight:bold">var</span> $e = $(<span style="color:#080;font-weight:bold">this</span>);
<span style="color:#080;font-weight:bold">if</span> ($e.is(<span style="color:#d20;background-color:#fff0f0">":hidden"</span>)) <span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">var</span> wt = $w.scrollTop(),
wb = wt + $w.height(),
et = $e.offset().top,
eb = et + $e.height();
<span style="color:#080;font-weight:bold">return</span> eb >= wt - th && et <= wb + th;
});
loaded = inview.trigger(<span style="color:#d20;background-color:#fff0f0">"lazy"</span>);
images = images.not(loaded);
}
$w.scroll(lazy);
$w.resize(lazy);
lazy();
<span style="color:#080;font-weight:bold">return</span> <span style="color:#080;font-weight:bold">this</span>;
};
})(<span style="color:#038">window</span>.jQuery);
$(<span style="color:#038">document</span>).ready(<span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#d20;background-color:#fff0f0">'.lazy'</span>).each(<span style="color:#080;font-weight:bold">function</span> () {
$(<span style="color:#080;font-weight:bold">this</span>).lazy(<span style="color:#00d;font-weight:bold">0</span>, <span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#080;font-weight:bold">this</span>).load(<span style="color:#080;font-weight:bold">function</span>() {
<span style="color:#080;font-weight:bold">this</span>.style.opacity = <span style="color:#00d;font-weight:bold">1</span>;
});
});
});
<span style="color:#888">// Set the correct attribute when printing
</span><span style="color:#888"></span><span style="color:#080;font-weight:bold">var</span> beforePrint = <span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#d20;background-color:#fff0f0">"img.lazy"</span>).each(<span style="color:#080;font-weight:bold">function</span>() {
$(<span style="color:#080;font-weight:bold">this</span>).trigger(<span style="color:#d20;background-color:#fff0f0">"lazy"</span>);
<span style="color:#080;font-weight:bold">this</span>.style.opacity = <span style="color:#00d;font-weight:bold">1</span>;
});
};
<span style="color:#080;font-weight:bold">if</span> (<span style="color:#038">window</span>.matchMedia) {
<span style="color:#080;font-weight:bold">var</span> mediaQueryList = <span style="color:#038">window</span>.matchMedia(<span style="color:#d20;background-color:#fff0f0">'print'</span>);
mediaQueryList.addListener(<span style="color:#080;font-weight:bold">function</span>(mql) {
<span style="color:#080;font-weight:bold">if</span> (mql.matches)
beforePrint();
});
}
<span style="color:#038">window</span>.onbeforeprint = beforePrint;
</code></pre></div><p>This script will search for all <code><img></code> tags with class <code>lazy</code>, and change the <code>data-src</code> attribute to the <code>src</code> attribute once the image becomes visible due to scrolling. It also includes some additional logic to set the <code>src</code> attribute before printing the page.</p>
<h3 id="server-side-caching">Server-side caching</h3>
<p>Instead of performing all the backend rendering calculations every time, server-side caching allows you to output the same content to the clients over a period of time from a temporary copy of the response. This not only results in a decreased response time but also saves some resources on the server.</p>
<p>There are several ways to enable server-side caching, depending on factors such as the backend language and hosting platform (e.g. Windows/IIS vs. Linux/Apache), among other things. For this example, we will use ASP.NET (C#) since I’m mostly a Windows user.</p>
<p>The best and most efficient way to do this is by adding a declaration in the top of our ASP.NET page:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><%<span style="color:#a61717;background-color:#e3d2d2">@</span> OutputCache Duration=<span style="color:#d20;background-color:#fff0f0">"10"</span> VaryByParam=<span style="color:#d20;background-color:#fff0f0">"id;date"</span> %>
</code></pre></div><p>This declaration is telling the compiler that we want to cache the output from the server for 10 minutes, and we will save different versions based on the <code>id</code> and <code>date</code> URL parameters. So pages like:</p>
<ul>
<li>https://www.your-url.com/cached-page/?id=1&date=2020-01-01</li>
<li>https://www.your-url.com/cached-page/?id=2&date=2020-01-01</li>
<li>https://www.your-url.com/cached-page/?id=2&date=2020-02-01</li>
</ul>
<p>will be saved and then served from different cache copies. If we only set the <code>id</code> parameter as a source for caching, pages with different dates will be served from the same cache source (this can be useful as the <code>date</code> parameter is only evaluated on frontend scripts and ignored in the backend).</p>
<p>There are other configurations in ASP.NET to set our output cache policy. The output can be set to be based on the browser, the request headers, or even custom strings. <a href="https://www.c-sharpcorner.com/UploadFile/chinnasrihari/Asp-Net-mvc-framework-server-side-html-caching-techniques/">This page</a> has more useful information on this subject.</p>
<h3 id="gzip-compression">GZip compression</h3>
<p>GZip compression—when the client supports it—allows compressing the response before sending it over the network. In this way, more than 70% of the bandwidth can be saved when loading the website. Enabling GZip compression for dynamic and static content on a Windows Server with IIS is simple: Just go to the “Compression” section on the IIS Manager and check the options “Enable dynamic/static content compression”.</p>
<p><img src="/blog/2020/01/decreasing-website-load-time/enabling-compression-iis.jpg" alt="Enabling compression in IIS"></p>
<p>However, if you are running an ASP.NET MVC/WebForms website, this won’t be enough. For all backend responses to be compressed before sending them to the client, some custom code will also need to be added to the <code>global.asax</code> file in the website root:</p>
<p><strong>global.asax:</strong></p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-csharp" data-lang="csharp"><%<span style="color:#a61717;background-color:#e3d2d2">@</span> Application Language=<span style="color:#d20;background-color:#fff0f0">"C#"</span> %>
<script runat=<span style="color:#d20;background-color:#fff0f0">"server"</span>>
<span style="color:#080;font-weight:bold">void</span> Application_PreRequestHandlerExecute(<span style="color:#888;font-weight:bold">object</span> sender, EventArgs e)
{
HttpApplication app = sender <span style="color:#080;font-weight:bold">as</span> HttpApplication;
<span style="color:#888;font-weight:bold">string</span> acceptEncoding = app.Request.Headers[<span style="color:#d20;background-color:#fff0f0">"Accept-Encoding"</span>];
System.IO.Stream prevUncompressedStream = app.Response.Filter;
<span style="color:#080;font-weight:bold">if</span> (app.Context.CurrentHandler == <span style="color:#080;font-weight:bold">null</span>)
<span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (!(app.Context.CurrentHandler <span style="color:#080;font-weight:bold">is</span> System.Web.UI.Page ||
app.Context.CurrentHandler.GetType().Name == <span style="color:#d20;background-color:#fff0f0">"SyncSessionlessHandler"</span>) ||
app.Request[<span style="color:#d20;background-color:#fff0f0">"HTTP_X_MICROSOFTAJAX"</span>] != <span style="color:#080;font-weight:bold">null</span>)
<span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (acceptEncoding == <span style="color:#080;font-weight:bold">null</span> || acceptEncoding.Length == <span style="color:#00d;font-weight:bold">0</span>)
<span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (Request.ServerVariables[<span style="color:#d20;background-color:#fff0f0">"SCRIPT_NAME"</span>].ToLower().Contains(<span style="color:#d20;background-color:#fff0f0">".axd"</span>)) <span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (Request.ServerVariables[<span style="color:#d20;background-color:#fff0f0">"SCRIPT_NAME"</span>].ToLower().Contains(<span style="color:#d20;background-color:#fff0f0">".js"</span>)) <span style="color:#080;font-weight:bold">return</span>;
<span style="color:#080;font-weight:bold">if</span> (Request.QueryString.ToString().Contains(<span style="color:#d20;background-color:#fff0f0">"_TSM_HiddenField_"</span>)) <span style="color:#080;font-weight:bold">return</span>;
acceptEncoding = acceptEncoding.ToLower();
<span style="color:#080;font-weight:bold">if</span> (acceptEncoding.Contains(<span style="color:#d20;background-color:#fff0f0">"deflate"</span>) || acceptEncoding == <span style="color:#d20;background-color:#fff0f0">"*"</span>)
{
app.Response.Filter = <span style="color:#080;font-weight:bold">new</span> System.IO.Compression.DeflateStream(prevUncompressedStream,
System.IO.Compression.CompressionMode.Compress);
app.Response.AppendHeader(<span style="color:#d20;background-color:#fff0f0">"Content-Encoding"</span>, <span style="color:#d20;background-color:#fff0f0">"deflate"</span>);
}
<span style="color:#080;font-weight:bold">else</span> <span style="color:#080;font-weight:bold">if</span> (acceptEncoding.Contains(<span style="color:#d20;background-color:#fff0f0">"gzip"</span>))
{
app.Response.Filter = <span style="color:#080;font-weight:bold">new</span> System.IO.Compression.GZipStream(prevUncompressedStream,
System.IO.Compression.CompressionMode.Compress);
app.Response.AppendHeader(<span style="color:#d20;background-color:#fff0f0">"Content-Encoding"</span>, <span style="color:#d20;background-color:#fff0f0">"gzip"</span>);
}
}
</script>
</code></pre></div><p>To make sure our code is working properly, an external tool like <a href="https://www.giftofspeed.com/gzip-test/">this</a> will inform you if GZip is enabled or not.</p>
<p><img src="/blog/2020/01/decreasing-website-load-time/gzip-compression-enabled.jpg" alt="It works!"></p>
<h3 id="summary">Summary</h3>
<p>While there are many ways of decreasing the load time of a website, most are common and expensive. However, with a few minor tweaks, we can offer a better user experience in addition to improve our position in the search engine results. Every bit of optimization counts towards the goal with SEO. Load time is a very important factor (to both the developer and the user), especially on mobile platforms where users expect to get what they want instantly.</p>
<p>The image below is a Google Analytics report from one of my websites where, over several months, I implemented most of these formulas. A month ago, I made the latest change of deferring ad loading, which had an observable impact on the average loading speed of the page:</p>
<p><img src="/blog/2020/01/decreasing-website-load-time/analytics-average-page-load.jpg" alt="Report from Google Analytics"></p>
<p>Do you have any other page load optimization techniques? <b>Leave a comment below!</b></p>
Designing for SEO from the Starthttps://www.endpointdev.com/blog/2018/03/designing-for-seo-from-the-start/2018-03-28T00:00:00+00:00Jon Allen
<p style="clear: both; text-align: right"><img border="0" src="/blog/2018/03/designing-for-seo-from-the-start/analytics-2.png" /></p>
<p><strong>Search engine optimization (SEO)</strong> is critical to the success of your website, and therefore critical to the success of your business. A high Google ranking means more page views—and more conversions. Google rewards websites that are user-friendly and easy-to-navigate, with fresh content and frequent updates. At End Point, we design with SEO in mind from the beginning of the project to help you get the most value from your online presence.</p>
<p>These are the five main areas that we focus on to improve your ranking:</p>
<ol>
<li>
<p><strong>Content Strategy</strong>—You want to provide your users with quality content. We can provide a content strategy to help ensure that your site stays on target, with clear copy, focused messaging, and consistent branding. We’ll help you put together a site that gets visitors where they want to be—and you’ll reap the rewards with an increase in traffic to your site.</p>
</li>
<li>
<p><strong>Sitemap, Keywords, and Semantic Markup</strong>—We dive into the nuts and bolts of your site to make sure that it can be crawled and indexed easily by Google. We produce a prioritized XML sitemap, relevant, long-tail keywords and metadata, descriptive page headings, titles, and URLs. Your site’s code will utilize HTML5 semantic markup to produce a well-structured, hierarchical document that will be easily read by web crawlers—and humans, too.</p>
</li>
<li>
<p><strong>Blogging and Social Media</strong>—Keeping your website fresh with new links and content is critical for SEO success. Setting up a blog platform makes it easy to quickly generate new content that can draw more visitors to your site. We can also connect you with social media platforms such as Twitter and Facebook to help carry your messages across the web.</p>
</li>
<li>
<p><strong>Analytics</strong>—Google Analytics allows you to watch in real time how users are finding and interacting with your site. Figure out which pages perform the best, which are underperforming, identify trends in user behavior, and calibrate your site to help users get the most out of their experience.</p>
</li>
<li>
<p><strong>Mobile Support</strong>—With just over <a href="https://www.statista.com/statistics/277125/share-of-website-traffic-coming-from-mobile-devices/" target="_blank">half</a> of all web traffic coming from mobile devices, it’s crucial to have a website that’s responsive on any device. Because of the ubiquity of mobile browsing, Google rewards sites that are mobile friendly. We optimize your site to ensure that pages load quickly and display on any laptop, tablet, or smartphone.</p>
</li>
</ol>
<p>There’s no big secret to a strong SEO strategy. It’s all about having engaging, useful content that’s easy to find and navigate. End Point has the knowledge and resources to help your business grow by building you a site that’s clear and focused, and one that’s up to the latest web standards.</p>
<p><strong>Here are a few guides that provide more details about what goes into a strong SEO strategy:</strong></p>
<ul>
<li><a href="https://moz.com/beginners-guide-to-seo" target="_blank">Moz Beginner’s Guide to SEO</a></li>
<li><a href="https://support.google.com/webmasters/answer/7451184?hl=en" target="_blank">Google SEO Starter Guide</a></li>
<li><a href="http://schema.org/" target="_blank">Schema.org</a></li>
</ul>
Improve SEO URLs for Interchange search pageshttps://www.endpointdev.com/blog/2016/01/improve-seo-urls-for-interchange-search/2016-01-27T00:00:00+00:00Jeff Boes
<p>This is an article aimed at beginner-to-intermediate Interchange developers.</p>
<p>A typical approach to a hierarchical Interchange site is:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">Categories -> Category -> Product
</code></pre></div><p>I.e., you list all your categories as links, each of which opens up a search results page filtering the products by category, with links to the individual product pages via the flypage.</p>
<p>Recently I upgraded a site so the category URLs were a bit more SEO-friendly. The original category filtering search produced these lovely specimens:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">/search.html?fi=products&st=db&co=1&sf=category&se=Shoes&op=rm
&sf=inactive&se=yes&op=ne&tf=category&ml=100
</code></pre></div><p>but what I really wanted was:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">/cat/Shoes.html
</code></pre></div><p>Such links are easier to communicate to users, more friendly to search engines, less prone to breakage (e.g., by getting word-wrapped in email clients), and avoid exposing details of your application (here, we’ve had to admit publicly that we have a table called “products” and that some items are “inactive”; a curious user might decide to see what happens if they change “sf=inactive&se=yes” to some other expression).</p>
<p>Here’s how I attacked this.</p>
<h3 id="creating-a-category-listing-page">Creating a category listing page</h3>
<p>First, I copied my “results.html” page to “catpage.html”. That way, my original search results page can continue to serve up ad hoc search results.</p>
<p>The search results were displayed via:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">[search-region]
...
[/search-region]
</code></pre></div><p>I converted this to a database query:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">[query sql="SELECT * FROM products WHERE NOT inactive AND category = [sql-quote][cgi category][/sql-quote]"
type=list prefix=item]
...
[/query]
</code></pre></div><p>I chose to use a prefix other than the default since it would avoid having to change so many tags in the page, and now both the original search page and new catpage would look much the same internally (and thus, if desired, I could refactor them in the future).</p>
<p>Note that I’ve defined part of the API for this page: the category to be searched is set in a CGI variable called “category”.</p>
<p>In my specific case, there was additional tinkering with this tag, because I had nested [query] tags already in the page within the search-region.</p>
<h3 id="creating-a-cat-actionmap">Creating a “cat” actionmap</h3>
<p>In order to translate a URL containing SEO-friendly “/cat/Shoes.html” into my search, I need an actionmap. Here’s mine; it’s very simple.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-perl" data-lang="perl">Actionmap cat <span style="color:#d20;background-color:#fff0f0"><<"</span><span style="color:#d20;background-color:#fff0f0">CODE</span><span style="color:#d20;background-color:#fff0f0">"
</span><span style="color:#d20;background-color:#fff0f0">sub {
</span><span style="color:#d20;background-color:#fff0f0"> my $url = shift;
</span><span style="color:#d20;background-color:#fff0f0"> my @url_parts = split '/' => $url;
</span><span style="color:#d20;background-color:#fff0f0"> shift @url_parts if $url_parts[0] eq 'cat';
</span><span style="color:#d20;background-color:#fff0f0">
</span><span style="color:#d20;background-color:#fff0f0"> $CGI->{mv_nextpage} = 'catpage.html';
</span><span style="color:#d20;background-color:#fff0f0"> $CGI->{category} = shift @url_parts;
</span><span style="color:#d20;background-color:#fff0f0"> return 1;
</span><span style="color:#d20;background-color:#fff0f0">}
</span><span style="color:#d20;background-color:#fff0f0"></span><span style="color:#d20;background-color:#fff0f0">CODE</span>
</code></pre></div><p>Actionmaps are called when Interchange detects that a URL begins with the actionmap’s name; here “cat”. They are passed a parameter containing the URL fragment (after removing all the site stuff). Here, that would be (e.g.) “/cat/Shoes”. We massage the URL to get our category code, and set up the page to be called along with the CGI parameter(s) it expects.</p>
<h3 id="cleaning-up-the-links">Cleaning up the links</h3>
<p>At the start of this article I noted that I may have a page listing all my categories. In my original setup, this generated links using a construction like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">a</span> <span style="color:#369">href</span>=<span style="color:#d20;background-color:#fff0f0">"[area href=search form=|fi=products
</span><span style="color:#d20;background-color:#fff0f0"> st=db
</span><span style="color:#d20;background-color:#fff0f0"> sf=category
</span><span style="color:#d20;background-color:#fff0f0"> se=Shoes
</span><span style="color:#d20;background-color:#fff0f0"> tf=category
</span><span style="color:#d20;background-color:#fff0f0"> ml=100|]"</span>>
Shoes
</<span style="color:#b06;font-weight:bold">a</span>>
</code></pre></div><p>Now my links are the much simpler:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><<span style="color:#b06;font-weight:bold">a</span> <span style="color:#369">href</span>=<span style="color:#d20;background-color:#fff0f0">"[area cat/Shoes]"</span>>Shoes</<span style="color:#b06;font-weight:bold">a</span>>
</code></pre></div><p>In my specific case, these links were generated within a [query] loop, but the approach is the same.</p>
<p>Note: the <a href="http://demo.icdevgroup.org/demo1/">Strap demo</a> supports SEO-friendly URLs out-of-the-box, and that it is included with the latest <a href="http://www.icdevgroup.org/i/dev/news?mv_arg=00060">Interchange 5.10</a> release.</p>
Google Sitemap rapid deploymenthttps://www.endpointdev.com/blog/2013/03/google-sitemap-rapid-deployment/2013-03-21T00:00:00+00:00Jeff Boes
<p>I was going to call this “Quick and Dirty Sitemaps”, but “Rapid Deployment” sounds more buzz-word-worthy. This is how to get a Google sitemap up and running quickly, using the Google sitemap generator and the Web Developer Firefox plug-in.</p>
<p>I had occasion to set up a sitemap using the <a href="https://code.google.com/archive/p/googlesitemapgenerator/">Google sitemap generator</a> for a site recently. Here’s what I did:</p>
<p>Download the generator using the excellent documentation found at the previous link. Unpack it into a convenient location and copy the example_config.xml file to something else, e.g., <a href="http://www.mysite.com_config.xml">www.mysite.com_config.xml</a>. Edit the new configuration file and:</p>
<ol>
<li>Modify the “base_url” setting to your site;</li>
<li>Change the “store_into” setting to a file in your site’s document root;</li>
<li>Add a pointer to a file that will contain your list-of-links, e.g.,</li>
</ol>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-xml" data-lang="xml"><span style="color:#b06;font-weight:bold"><urllist</span> <span style="color:#369">path=</span><span style="color:#d20;background-color:#fff0f0">"site_urls.txt"</span><span style="color:#b06;font-weight:bold">></span>
<span style="color:#b06;font-weight:bold"></urllist></span>
</code></pre></div><p>I would locate this in the same path as your new configuration file.</p>
<p>Now, if you don’t already have <a href="https://chrispederick.com/work/web-developer/firefox/">Web Developer</a>, give yourself a demerit and go install it.</p>
<p>…</p>
<p>Okay, you’ll thank me for that. Now pick a few pages from your site: good choices, depending on your site’s design, are the home page, the sitemap (if you have one), and any of the top-level “nav links” you may have set up.</p>
<p>Visit each of those pages in turn. Use Web Developer to assemble the links from the pages, clicking:</p>
<ol>
<li>Tools menu</li>
<li>Web Developer extension</li>
<li>Information</li>
<li>View link information</li>
</ol>
<p>Copy and paste each informational list-of-links and append it to a text file. You can clean it up a bit when you are done, removing any links you don’t want in the sitemap, or you can let the sitemap generator tell you which ones to remove while testing.</p>
<p>You can sort and de-duplicate the file with something like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ sort site_urls.txt | uniq > site_urls.out
</code></pre></div><p>Inspect the site_urls.out file and when you’re happy with it, rename it to “site_urls.txt”.</p>
<p>You’re ready to run the sitemap generator:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">$ python sitemap_gen.py --config=www.mysite.com_config.xml --testing
</code></pre></div><p>Check the output for warnings, adjust the configuration and/or the site_urls.txt file, and eventually you can run this without the –testing flag. Now you just need to add it to a crontab where it will be run appropriately, and you’re done!</p>
Slash URLhttps://www.endpointdev.com/blog/2012/12/slash-url/2012-12-04T00:00:00+00:00Jeff Boes
<p>There’s always more to learn in this job. Today I learned that <a href="http://www.jampmark.com/web-scripting/5-solutions-to-url-encoded-slashes-problem-in-apache.html">Apache web server</a> is smarter than me.</p>
<p>A typical SEO-friendly solution to Interchange pre-defined searches (item categories, manufacturer lists, etc.) is to put together a URL that includes the search parameter, but looks like a hierarchical URL:</p>
<p>/accessories/Mens-Briefs.html</p>
<p>/manufacturer/Hanes.html</p>
<p>Through the magic of <a href="http://interchange.rtfm.info/icdocs/config/ActionMap.html">actionmaps</a>, we can serve up a search results page that looks for products which match on the “accessories” or “manufacturer” field. The problem comes when a less-savvy person adds a field value that includes a slash:</p>
<p>accessories: “Socks/Hosiery”</p>
<p>or</p>
<p>manufacturer: “Disney/Pixar”</p>
<p>Within my actionmap Perl code, I wanted to redirect some URLs to the canonical actionmap page (because we were trying to short-circuit a crazy Web spider, but that’s beside the point). So I ended up (after several wild goose chases) with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-perl" data-lang="perl"><span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$new_path</span> = <span style="color:#d20;background-color:#fff0f0">'/accessories/'</span> .
<span style="color:#b06;font-weight:bold">Vend::Tags</span>->filter({body => (<span style="color:#038">join</span> <span style="color:#d20;background-color:#fff0f0">'%2f'</span> => (<span style="color:#038">grep</span> { <span style="color:#080;background-color:#fff0ff">/\D/</span> } <span style="color:#369">@path</span>)),
op => <span style="color:#d20;background-color:#fff0f0">'urlencode'</span>, }) .
<span style="color:#d20;background-color:#fff0f0">'.html'</span>;
</code></pre></div><p>By this I mean: I put together my path out of my selected elements, joined them with a URL-encoded slash character (%2f), and then further URL-encoded the result. This was counter-intuitive, but as you can see at the first link in this article, it’s necessary because Apache is smarter than you. Well, than me anyway.</p>
An Introduction to Google Website Optimizerhttps://www.endpointdev.com/blog/2012/04/google-website-optimizer-introduction/2012-04-17T00:00:00+00:00Steph Skardal
<p>On End Point’s website, <a href="/team/jon-jensen/">Jon</a> and I recently discussed testing out use of Google Website Optimizer to run a few A/B tests on content and various website updates. I’ve worked with a couple of clients who use Google Website Optimizer, but I’ve never installed it from start to finish. Here are a few basic notes that I made during the process.</p>
<h3 id="whats-the-point">What’s the Point?</h3>
<p>Before I get into the technical details of the implementation, I’ll give a quick summary of why you would want to A/B test something. A basic A/B test will test user experiences of content A versus content B. The goal is to decide which of the two (content A or content B) leads to higher conversion (or higher user interactivity that indirectly leads to conversion). After testing, one would continue to use the higher converting content. An example of this in ecommerce may be product titles or descriptions.</p>
<h3 id="ab-tests-in-google-website-optimizer">A/B tests in Google Website Optimizer</h3>
<p>I jumped right into the Google Website Optimizer sign-up and wanted to set up a simple A/B test to test variations on our home page content. Unfortunately, I found right away that basic A/B tests in Google Website optimizer require two different URLs to test. In test A, the user would be see index.html, and in test B, the user would see index_alt.html. This is unfortunate because for SEO and technical reasons, I didn’t want to create an alternative index page.</p>
<table cellpadding="0" cellspacing="0" width="100%">
<tbody><tr>
<td valign="bottom"><img border="0" src="/blog/2012/04/google-website-optimizer-introduction/image-0.png" width="350"/></td>
<td valign="bottom"><img border="0" src="/blog/2012/04/google-website-optimizer-introduction/image-1.png" width="350"/>
</td>
</tr>
<tr>
<td>
<p><small>Test A: Keep existing text</small></p>
</td>
<td>
<p><small>Test B: Remove some paragraph text in first section</small></p>
</td>
</tr>
</tbody></table>
<h3 id="multivariate-testing">Multivariate Testing</h3>
<p>Rather than implement a basic A/B tests in Google Website optimizer, I decided to implement a multivariate test with just the two options (A and B). The basic setup required this:</p>
<ul>
<li>Copy provided JavaScript into my test page just above <code></head></code></li>
<li>Wrap <code><script>utmx_section("stephs_test")</script></code>…<code></noscript></code> around the section of text that will be modified by the test.</li>
<li>Copy provided JavaScript into my converting test page just above <code></head></code></li>
</ul>
<p>Google Website Optimizer will verify the code and provide a user interface to enter the variations of the test text.</p>
<p>I used multivariate testing to test the homepage changes described above. After a couple of weeks of testing, my test results were inconclusive:</p>
<p><img border="0" height="191" src="/blog/2012/04/google-website-optimizer-introduction/image-2.png" width="400"/><br/>
<small>Example multivariate test result in Google Website Optimizer</small></p>
<h3 id="a-limitation-with-multivariate-testing">A Limitation with Multivariate Testing</h3>
<p>One thing we wanted to test was a site-wide CSS change. Unfortunately, the multivariate testing in place is designed to test on page content only rather than global CSS changes. You could potentially come up with a “creative” hack and set a cookie inside the variation to specify which layout option you would use. And then the page would always look at that cookie while rendering to apply the special CSS behavior. However, this requires a bit of customization and development.</p>
Interchange Search Caching with “Permanent More”https://www.endpointdev.com/blog/2012/01/interchange-search-caching-with/2012-01-02T00:00:00+00:00Mark Johnson
<p>Most sites that use Interchange take advantage of Interchange’s “more lists”. These are built-in tools that support an Interchange “search” (either the search/scan action, or result of direct SQL via [query]) to make it very easy to paginate results. Under the hood, the more list is a drill-in to a cached “search object”, so each page brings back a slice from the cache of the original search. There are extensive ways to modify the look and behavior of more lists and, with a bit of effort, they can be configured to meet design requirements.</p>
<p>Where more lists tend to fall short, however, is with respect to SEO. There are two primary SEO deficiencies that get business stakeholders’ attention:</p>
<ul>
<li>There is little control over the construction of the URLs for more lists. They leverage the scan actionmap and contain a hash key for the search object and numeric data to identify the slice and page location. They possess no intrinsic value in identifying the content they reference.</li>
<li>The search cache by default is ephemeral and session-specific. This means all those results beyond page 1 the search engine has cataloged will result in dead links for search users who try to land directly on the more-listed pages.</li>
</ul>
<p>It is the latter issue that I wish to address because there is—and has been for some time now—a simple mechanism called “permanent more” to remedy the default behavior.</p>
<p>You can leverage “permanent more” by adding the boolean <strong>mv_more_permanent</strong>, or the shorthand <strong>pm</strong>, to your search conditions. E.g.:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">Link:
<a href="[area search="
co=1
sf=category
se=Foo
op=rm
more=1
ml=5
<b>pm=1</b>
"]">All Foos</a>
Loop:
[loop search="
co=1
sf=category
se=Foo
op=rm
more=1
ml=5
<b>pm=1</b>
"]
...loop body with [more-list]...
[/loop]
Query:
[query
list=1
more=1
ml=10
<b>pm=1</b>
sql="SELECT * FROM products WHERE category LIKE '%Foo%'"
]
...same as loop but with 10 matches/page...
[/query]
</code></pre></div><p>If the initial search is defined with the “permanent more” setting, it will produce the following adjustments:</p>
<ul>
<li>The hash key used to store and identify the search cache is deterministic based on the search conditions. Many searches for Interchange are category driven. Thus, all end users who wish to browse a category end up clicking identical links, which create duplicate search caches, belonging uniquely to them. With permanent more, they all share the same cache, with the same identifier. As long as the search conditions don’t change, neither does the cache identifier. Even as the cache is refreshed with new executions of the search, the object remains in the same location. Thus, the results a search engine produced this morning reference links still valid now, tomorrow, or next week, provided they reference the same search conditions.</li>
<li>The cached search object has no session affinity. Any link referencing the cache with the correct hash key has access to the content.</li>
</ul>
<p>Taken together, “permanent more” removes (for the most part, addressed later) dead links from more lists cataloged by search engines. There are, however, other benefits to “permanent more” beyond those intended as described above:</p>
<ul>
<li>As stated in passing, standard Interchange search caching produces duplicate search objects for common search conditions. For a busy site, these caches can have an impact on storage. Typically, maintenance is implemented to clean up cache files for all such files whose age exceeds by some amount the session duration (standard is 48 hours). With permanent more, duplicate caches are eliminated. A cache location is reused by all users with the same search requirements, keeping data-storage requirements for caches to the minimum necessary. As searches change, ophaned caches can still easily be cleaned up as they will immediately start to age with no more access to them necessary for storage.</li>
<li>For the same reason that “permanent more” resolves search-engine links, it also resolves content management for individual sites using a reverse proxy for caching. Because most (and certainly the easiest) caching keys are based off of URL, the deterministic nature of the hash keys for “permanent more” allows assurance that the cached content in the proxy accurately reflects the search content over time, and that all users will hit the cached resource and not generate new, unique links with varying hash keys.</li>
</ul>
<p>One shortcoming of “permanent more” to be aware of is the impact of changing data underneath the search. Even if search conditions do not change, the count and order of matching record sets may. So, e.g., enough products may be removed from a given category to cause the last page of a more list to become empty, which would cause any specific link into that page to become dead. More minor, but still a possibility, is the introduction or removal of products so that a particularly searched-for term has been “bumped” to another page within the search cache since the last time the search engine crawled the more lists. For searches backed by particularly volatile data, “permanent more” may not be sufficient to address search-engine or caching demands.</p>
<p>Finally, “permanent more” should be avoided for any search features that may cache data sensitive to an individual user. This is unlikely to happen as, under most circumstances, the configuration of the search itself will change based on the unique characteristics of the user executing the search (e.g., a username included in a query to review order history). However, it is still possible that context-sensitive information could be stored in the search object and, if so, all other users with access to the more lists would have access to that information.</p>
SEO friendly redirects in Interchangehttps://www.endpointdev.com/blog/2010/10/seo-friendly-redirects-in-interchange/2010-10-15T00:00:00+00:00Richard Templet
<p>In the past, I’ve had a few <a href="http://www.icdevgroup.org/i/dev">Interchange</a> clients that would like the ability to be able to have their site do a SEO friendly 301 redirect to a new page for different reasons. It could be because either a product had gone out of stock and wasn’t going to return or they completely reworked their url structures to be more SEO friendly and wanted the link juice to transfer to the new URLs. The normal way to handle this kind of request is to set up a bunch of Apache rewrite rules.</p>
<p>There were a few issues with going that route. The main issue is that to add or remove rules would mean that we would have to restart or reload Apache every time a change was made. The clients don’t normally have the access to do this so it meant they would have to contact me to do it. Another issue was that they also don’t have the access to modify the Apache virtual host file to add and remove rules so again, they would have to contact me to do it. To avoid the editing issue, we could have put the rules in a .htaccess file and allow them to modify it that way, but this can present its own challenges because some text editors and FTP clients don’t handle hidden files very well. The other issue is that even though overall basic rewrite rules are pretty easy to copy, paste and reuse, they still can have nasty side effects if not done properly and can also be difficult to troubleshoot so I devised a way to allow them to be able to manage their 301 redirects using a simple database table and Interchange’s Autoload directive.</p>
<p>The database table is a very simple table with two fields. I called them old_url and new_url with the primary key being old_url. The Autoload directive accepts a list of subroutines as its arguments so this requires us to create two different GlobalSubs. One to actually do the redirect and one to check the database and see if we need to redirect. The redirect sub is really straight forward and looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-perl" data-lang="perl"><span style="color:#080;font-weight:bold">sub</span> <span style="color:#06b;font-weight:bold">redirect</span> {
<span style="color:#080;font-weight:bold">my</span> (<span style="color:#369">$url</span>, <span style="color:#369">$status</span>) = <span style="color:#369">@_</span>;
<span style="color:#369">$status</span> ||= <span style="color:#00d;font-weight:bold">302</span>;
<span style="color:#369">$</span><span style="color:#b06;font-weight:bold">Vend::</span><span style="color:#369">StatusLine</span> = <span style="color:#2b2;background-color:#f0fff0">qq|Status: $status moved\nLocation: $url\n|</span>;
<span style="color:#369">$::Pragma</span>->{download} = <span style="color:#00d;font-weight:bold">1</span>;
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$body</span> = <span style="color:#d20;background-color:#fff0f0">''</span>;
::response(<span style="color:#369">$body</span>);
<span style="color:#369">$</span><span style="color:#b06;font-weight:bold">Vend::</span><span style="color:#369">Sent</span> = <span style="color:#00d;font-weight:bold">1</span>;
<span style="color:#080;font-weight:bold">return</span> <span style="color:#00d;font-weight:bold">1</span>;
}
</code></pre></div><p>The code for the sub that checks to see if we need to redirect looks like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-perl" data-lang="perl"><span style="color:#080;font-weight:bold">sub</span> <span style="color:#06b;font-weight:bold">redirect_old_links</span> {
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$db</span> = <span style="color:#b06;font-weight:bold">Vend::Data::</span>database_exists_ref(<span style="color:#d20;background-color:#fff0f0">'page_redirects'</span>);
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$dbh</span> = <span style="color:#369">$db</span>->dbh();
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$current_url</span> = <span style="color:#369">$::Tag</span>->env({ arg => <span style="color:#d20;background-color:#fff0f0">"REQUEST_URI"</span> });
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$normal_server</span> = <span style="color:#369">$::Variable</span>->{NORMAL_SERVER};
<span style="color:#080;font-weight:bold">if</span> ( ! <span style="color:#038">exists</span> <span style="color:#369">$::Scratch</span>->{redirects} ) {
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$sth</span> = <span style="color:#369">$dbh</span>->prepare(<span style="color:#2b2;background-color:#f0fff0">q{select * from page_redirects}</span>);
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$rc</span> = <span style="color:#369">$sth</span>->execute();
<span style="color:#080;font-weight:bold">while</span> ( <span style="color:#080;font-weight:bold">my</span> (<span style="color:#369">$old</span>,<span style="color:#369">$new</span>) = <span style="color:#369">$sth</span>->fetchrow_array() ) {
<span style="color:#369">$::Scratch</span>->{redirects}{<span style="color:#d20;background-color:#fff0f0">"$old"</span>} = <span style="color:#369">$new</span>;
}
<span style="color:#369">$sth</span>->finish();
}
<span style="color:#080;font-weight:bold">if</span> ( <span style="color:#038">exists</span> <span style="color:#369">$::Scratch</span>->{redirects} ) {
<span style="color:#080;font-weight:bold">if</span> ( <span style="color:#038">exists</span> <span style="color:#369">$::Scratch</span>->{redirects}{<span style="color:#d20;background-color:#fff0f0">"$current_url"</span>} ) {
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$path</span> = <span style="color:#369">$normal_server</span>.<span style="color:#369">$::Scratch</span>->{redirects}{<span style="color:#d20;background-color:#fff0f0">"$current_url"</span>};
<span style="color:#080;font-weight:bold">my</span> <span style="color:#369">$Sub</span> = <span style="color:#b06;font-weight:bold">Vend::Subs</span>-><span style="color:#080;font-weight:bold">new</span>;
<span style="color:#369">$Sub</span>->redirect(<span style="color:#369">$path</span>, <span style="color:#d20;background-color:#fff0f0">'301'</span>);
<span style="color:#080;font-weight:bold">return</span>;
} <span style="color:#080;font-weight:bold">else</span> {
<span style="color:#080;font-weight:bold">return</span>;
}
}
}
</code></pre></div><p>We normally create these as two different files and put them into our own directory structure under the Interchange directory called custom/GlobalSub and then add this, include custom/GlobalSub/*.sub, to the interchange.cfg file to make sure they get loaded when Interchange restarts. After those files are loaded, you’ll need to tell the catalog that you want it to Autoload this subroutine and to do that you use the Autoload directive in your catalog.cfg file like this:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">Autoload redirect_old_links
</code></pre></div><p>After modifying your catalog.cfg file, you will need to reload your catalog to ensure to change takes effect. Once these things are in place, you should just be able to add data into the page_redirects table and start a new session and it will redirect you properly. When I was working on the system, I just created an entry that redirected /cgi-bin/vlink/redirect_test.html to /cgi-bin/vlink/index.html so I could ensure that it was redirecting me properly.</p>
More Code and SEO with the Google Analytics APIhttps://www.endpointdev.com/blog/2010/02/code-seo-google-analytics-api/2010-02-22T00:00:00+00:00Steph Skardal
<p>My latest blog article inspiration came from an <a href="https://moz.com/">SEOmoz</a> pro webinar on Actionable Analytics. This time around, I wrote the article and it was published on SEOmoz’s YOUmoz Blog and I thought I’d summarize and extend the article here with some technical details more appealing to our audience. The article is titled <a href="https://moz.com/ugc/visualizing-keyword-data-with-the-google-analytics-api">Visualizing Keyword Data with the Google Analytics API</a>.</p>
<p>In the article, I discuss and show examples of how the number of unique keywords receiving search traffic has diversified or expanded over time and that our SEO efforts (including writing blog articles) are likely resulting in this diversification of keywords. Some snapshots from the articles:</p>
<p><img src="/blog/2010/02/code-seo-google-analytics-api/image-0.png" />
<img src="/blog/2010/02/code-seo-google-analytics-api/image-1.png" /></p>
<p>[The unique keyword (keywords receiving at least one search visit) count per month (top) compared to the number of articles available on our blog at that time (bottom).]</p>
<p>I also briefly examined how unique keywords receiving at least one visit overlapped between each month and saw about 10-20% of overlapping keywords (likely the short-tail of SEO).</p>
<p><img src="/blog/2010/02/code-seo-google-analytics-api/image-2.png" /></p>
<p>[The keyword overlap per month, where the keywords receiving at least one visit in consecutive months are shown in the overlap section.]</p>
<p>Now, on to things that End Point’s audience may find more interesting. Something that might appeal more to our developer-types is the code written to use the Google Analytics API to generate the data used for this article. I researched a bit and tried writing my own ruby code (gem-less) to pull from the Google API, followed by using the Gattica gem, and finally the garb gem. After wrestling with the former two options, I settled on the <a href="https://github.com/vigetlabs/garb">garb</a> gem, which had decent documentation <a href="https://github.com/vigetlabs/garb/wiki">here</a> to get me up and running with a Google Analytics report quickly. Here’s an example of the code required to create your first Google Analytics API report:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><span style="color:#888">#!/usr/bin/ruby</span>
<span style="color:#038">require</span> <span style="color:#d20;background-color:#fff0f0">'rubygems'</span>
<span style="color:#038">require</span> <span style="color:#d20;background-color:#fff0f0">'garb'</span>
<span style="color:#888"># set email, password, profile_id</span>
<span style="color:#036;font-weight:bold">Garb</span>::<span style="color:#036;font-weight:bold">Session</span>.login(email, password)
profile = <span style="color:#036;font-weight:bold">Garb</span>::<span style="color:#036;font-weight:bold">Profile</span>.first(profile_id)
report = <span style="color:#036;font-weight:bold">Garb</span>::<span style="color:#036;font-weight:bold">Report</span>.new(profile,
<span style="color:#a60;background-color:#fff0f0">:limit</span> => <span style="color:#00d;font-weight:bold">100</span>,
<span style="color:#a60;background-color:#fff0f0">:start_date</span> => <span style="color:#036;font-weight:bold">Date</span>.today - <span style="color:#00d;font-weight:bold">30</span>,
<span style="color:#a60;background-color:#fff0f0">:end_date</span> => <span style="color:#036;font-weight:bold">Date</span>.today)
report.dimensions <span style="color:#a60;background-color:#fff0f0">:keyword</span>
report.metrics <span style="color:#a60;background-color:#fff0f0">:visits</span>
report.results.each <span style="color:#080;font-weight:bold">do</span> |result|
<span style="color:#038">puts</span> <span style="color:#d20;background-color:#fff0f0">"</span><span style="color:#33b;background-color:#fff0f0">#{</span>result.keyword<span style="color:#33b;background-color:#fff0f0">}</span><span style="color:#d20;background-color:#fff0f0">:</span><span style="color:#33b;background-color:#fff0f0">#{</span>result.visits<span style="color:#33b;background-color:#fff0f0">}</span><span style="color:#d20;background-color:#fff0f0">"</span>
<span style="color:#080;font-weight:bold">end</span>
</code></pre></div><p>If you aren’t familiar with the Google Analtyics API, possible dimensions and metrics are documented <a href="https://developers.google.com/analytics/devguides/reporting/core/dimsmets">here</a>. There are some Google Analytics API limitations on metric and dimension combinations, but I think if you get creative you’d be able to overcome most of those limitations (assuming you won’t be exceeding the limit of 1,000 API requests per day).</p>
<p>Why should you care about the Google Analytics API? Well, the API allowed me to programmatically aggregate the keyword counts in monthly increments for the SEOmoz article. One thing I consider to be pretty lame is the inability to select more than 3 custom segments and exclude the “All Visits” segment to allow a better visual comparison of the segments. In the data below, I have 3 defined custom segments. I would prefer to compare about 10 custom segments of End Point’s blog keyword groupings (e.g., “Rails Keywords”, “Postgres Keywords”), but Google Analytics limits the selected segments and includes “All Visits” when you select more than one custom segment.</p>
<p><img src="/blog/2010/02/code-seo-google-analytics-api/image-3.png" /></p>
<p>Another thing I consider to be lame is the inability to merge Google Analytics profiles. Recently, End Point combined its corporate blog GA profile with its main website GA profile to better track conversion between the sites:</p>
<p><img src="/blog/2010/02/code-seo-google-analytics-api/image-4.png" /></p>
<p>[Dead metrics from migrated profile.]</p>
<p>With the Google Analytics API, we could compute different aggregates of data, compare more than a few custom data segments, and combine two google profiles if they have merged. Of course, these things wouldn’t necessarily be easy, but working with the gem proved to be simple, so in theory this all could be done and in the meantime we’ll keep our dead profile around.</p>
<p>Again, please read the original article <a href="https://moz.com/ugc/visualizing-keyword-data-with-the-google-analytics-api">here</a> if you are interested. :)</p>
Blog versus Forum, Blogger versus WordPress in Ecommercehttps://www.endpointdev.com/blog/2010/01/blog-vs-forum-blogger-vs-wordpress/2010-01-25T00:00:00+00:00Steph Skardal
<p><img src="https://s.w.org/style/images/about/WordPress-logotype-standard.png" alt=""></p>
<p>Today, Chris sent me an email with two questions for one of our ecommerce clients:</p>
<ul>
<li>For ecommerce client A, should a forum or blog be added?</li>
<li>For ecommerce client A, should the client use Blogger or WordPress if they add a blog?</li>
</ul>
<p>These are relevant questions to all of our clients because forums and blogs can provide value to a static site or ecommerce site. I answered Chris’ question and thought I’d expand on it a bit for a brief article.</p>
<p>First, a rundown comparing the pros and cons of blog versus forum:</p>
<table cellpadding="0" cellspacing="5" class="blog_article" width="100%">
<tbody><tr class="alt">
<td width="10%"> </td>
<td align="center" width="45%"><b>Blog</b></td>
<td align="center" width="45%"><b>Forum</b></td>
</tr>
<tr>
<td valign="top"><b>Pros</b></td>
<td valign="top">
<ul>
<li>Content tends to be more organized.</li>
<li>Content can be built to be more targeted for search.</li>
<li>Content can be syndicated easily.</li>
</ul>
</td>
<td valign="top">
<ul>
<li>There can be much more content because users are contributing content.</li>
<li>Since there is more user generated content, it has the potential to cover more of the <a href="https://en.wikipedia.org/wiki/Long_Tail">long tail</a> in search.</li>
<li>There is more potential for user involvement and encouragement to build and contribute to a community.</li>
</ul>
</td>
</tr>
<tr>
<td valign="top"><b>Cons</b></td>
<td valign="top">
<ul>
<li>User generated content will remain minimal if comments are the only form of user generated content in a blog.</li>
<li>If internal staff is responsible for authoring content, you can’t write as much content as users can contribute.</li>
</ul>
</td>
<td valign="top">
<ul>
<li>A forum requires management to prevent user spam.</li>
<li>A forum requires organization to maintain usability and search engine friendliness.</li>
</ul>
</td>
</tr></tbody></table>
<p>If we assume that it takes the same amount of effort to write articles as it does to manage user generated content, the decision comes down to whether or not you want to utilize user contributions as part of the content. If the effort involved to write content or manage user generated content is different, a decision should be made based on how much effort the site owners want to make. Other opportunities for user generated content include product reviews and user QnA.</p>
<p>Next, a rundown comparing the pros and cons of Blogger versus <strong>self-hosted</strong> WordPress:</p>
<table cellpadding="0" cellspacing="5" class="blog_article" width="100%">
<tbody><tr>
<td width="10%"> </td>
<td align="center" style="background-color:#002255;" width="45%"><b><a href="https://www.blogger.com"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5430766117686011266" src="/blog/2010/01/blog-vs-forum-blogger-vs-wordpress/image-0.png" style="margin:5px;width: 75px; height: 20px;"/></a></b></td>
<td align="center" style="background-color:#464646;" width="45%"><b><a href="https://wordpress.org"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5430766118456236978" src="/blog/2010/01/blog-vs-forum-blogger-vs-wordpress/image-0.png" style="margin:5px;width: 151px; height: 26px;"/></a></b></td></tr>
<tr><td valign="top"><b>Pros</b></td>
<td valign="top">
<ul>
<li>There are a decent amount of widgets available to integrate into a Blogger instance.</li>
<li>Fast Google indexing of content may result since the content is hosted by Google.</li>
<li>There is decent search implementation on Blogger.</li>
<li>A Blogger instance is very easy to create and easy to use.</li>
</ul>
</td>
<td valign="top">
<ul>
<li>There is a very large feature set available through the <a href="https://wordpress.org/plugins/">WordPress plugin</a> community.</li>
<li>Self-hosted WordPress blogs are relatively easy to set up. Many hosting platforms include WordPress installation and setup at the click of a button.</li>
<li>WordPress gives you control over the URL structure (articles, categories, tags) through permalinks.</li>
<li>Self-hosted WordPress can live at www.yoursite.com/blog/ which can strengthen your domain value in search through external links.</li>
<li>WordPress has a very flexible taxonomy system.</li>
</ul>
</td>
</tr>
<tr>
<td valign="top"><b>Cons</b></td>
<td valign="top">
<ul>
<li>The Blogger taxonomy system is limited (using labels) and labels pages are blocked in robots.txt to reduce indexation and search traffic of the label pages.</li>
<li>Blogger does not allow for a flexible URL structure. Once an article is published and a title is changed, the URL does not change.</li>
<li>Developers must be familiar with the Blogger template language to customize the template.</li>
<li>With Blogger, a blog can’t be hosted at http://www.yoursite.com/blog/. It can be hosted at http://blog.yoursite.com/. While this results in a strong subdomain, it does not strengthen your domain for search through external links to the blog.</li>
</ul>
</td>
<td valign="top">
<ul>
<li>Self-hosted WordPress requires your own hosting, setup and installation.</li>
<li>Self-hosted WordPress requires management of upgrades and plugins. Plugins may require code changes to the template files.</li>
<li>Self-hosted WordPress allows you to select existing themes, but you must be familiar with the WordPress template structure if you want a custom blog look.</li>
</ul>
</td>
</tr></tbody></table>
<p>The decision to create a Blogger blog or install a WordPress blog will depend on resources such as engineering or designer involvement. A self-hosted blog solution will likely provide a larger feature set and more flexibility, but it also requires more time to enhance, manage and maintain the software. A hosted blog solution such as Blogger will be easy to set up and maintain, but has disadvantages because it is a less flexible solution. I didn’t discuss a WordPress-hosted solution because I’m not very familiar with this type of setup, however, I believe the WordPress-hosted solution limits the use of plugins and themes.</p>
<p>For our ecommerce clients, installing a self-hosted WordPress instance on top of their Spree or <a href="/expertise/perl-interchange/">Interchange</a> ecommerce site has been relatively simple. For another one of our clients, we developed a <a href="http://radiantcms.org/">Radiant</a> plugin to integrate Blogger article links into their site, which has worked well to fit their needs.</p>
SEO 2010 Trends and Strategieshttps://www.endpointdev.com/blog/2010/01/seo-2010-trends/2010-01-22T00:00:00+00:00Steph Skardal
<p>Yesterday I attended <a href="https://moz.com/">SEOmoz’s</a> webinar titled “SEO Strategies for 2010”. Some interesting factoids, comments and resources for SEO in 2010 were presented that I thought I’d highlight:</p>
<ul>
<li>
<p>Mobile browser search</p>
<ul>
<li>Mobile search and ecommerce will be a large area of growth in 2010.</li>
<li>Google Webmaster Tools allows you to submit mobile sitemaps, which can help battle duplicate content between non-mobile and mobile versions of site content. Another way to handle duplicate content would be to write semantic HTML that allows sites to serve non-mobile and mobile CSS.</li>
</ul>
</li>
<li>
<p>Social Media: Real Time Search</p>
<ul>
<li>Real time search marked its presence in 2009. The involvement of Twitter in search is evolving.</li>
<li>Tracking and monitoring on URL shortening services should be set up to measure traffic and benefit from Twitter.</li>
<li>Dan Zarrella published research on <a href="https://www.slideshare.net/danzarrella/the-science-of-re-tweets">The Science of Retweeting</a>. This is an interesting resource with fascinating statistics on retweets.</li>
</ul>
</li>
<li>
<p>Social Media: Facebook’s Dominance</p>
<ul>
<li>Recent research by comScore has shown that 5.5% of all time on the web is spent in Facebook.</li>
<li>Facebook has very affordable advertising. Facebook has so much demographic and psychographic data that allows sites to deliver very targeted advertisements.</li>
<li>Facebook shouldn’t be ignored as a potential business network, but metrics should be put in place to determine the value it brings.</li>
</ul>
</li>
<li>
<p>Social Media: Shifting LinkGraph</p>
<ul>
<li>In the past, sites received links from blog resources which became a factor in the site’s popularity rankings in search. Now, linking has shifted to microblogging such as twitter or other social media platforms. Some folks are stingier about passing links through sites rather than social media. It’s interesting to observe how links and information is passed through the web and consider how this can affect search.</li>
</ul>
</li>
</ul>
<p><a href="https://3.bp.blogspot.com/_wWmWqyCEKEs/S1ouYvroyaI/AAAAAAAADFA/Pdlm-n22ikM/s1600-h/twitter_bird.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5429703303399786914" src="/blog/2010/01/seo-2010-trends/image-0.jpeg" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 276px;"/></a></p>
<ul>
<li>
<p>Bing</p>
<ul>
<li>Despite the fact that Google is responsible for a large percentage of search, Bing shouldn’t be ignored.</li>
<li>Bing has shown some differences in ranking such as being less sensitive to TLDs (.info, .cc, .net, etc.), and giving more weight to sites with keywords in the domain than other search engines.</li>
</ul>
</li>
<li>
<p>Other</p>
<ul>
<li>Personalized search is on the rise. This is something to pay attention to, but hard to measure.</li>
<li>QDF (query deserves freshness), a search factor related to the freshness of content, has led to search engines indexing content faster. 2010 search strategies recommend becoming a news source to improve search performance.</li>
<li>Local search is definitely something to be aware of in 2010. Google’s Place Rank algorithm is similar to the PageRank algorithm—it looks at specific location or local attributes as a factor in local search.</li>
</ul>
</li>
</ul>
<p>I found that a trend of the discussion revolved around having good metrics, not just good metrics, but the right metrics such as conversion and engagement. Testing any of the recommendations above (improving your mobile browsing, getting involved in social media, optimizing for Bing) should be measured against conversion to determine the value of the efforts. Also, multivariate or A/B testing were recommended for testing local search optimization and other efforts.</p>
Content Syndication, SEO, and the rel canonical Taghttps://www.endpointdev.com/blog/2009/12/content-syndication-seo-rel-canonical/2009-12-17T00:00:00+00:00Steph Skardal
<h3 id="end-point-blog-content-syndication">End Point Blog Content Syndication</h3>
<p>The past couple weeks, I’ve been discussing if content syndication of our blog negatively affects our search traffic with <a href="/team/jon-jensen/">Jon</a>. Since the blog’s inception, full articles have been syndicated by <a href="http://www.osnews.com/">OSNews</a>. The last couple weeks, I’ve been keeping an eye on the effects of content syndication on search to determine what (if any) negative effects we experience.</p>
<p>By my observations, immediately after we publish an article, the article is indexed by Google and is near the top search results for a search with keywords similar to the article’s title. The next day, OSNews syndication of the article shows up in the same keyword search, and our article disappears from the search results. Then, several days later, our article is ahead of OSNews as if Google’s algorithm has determined the original source of the content. I’ve provided visual representation of this behavior:</p>
<p><a href="http://3.bp.blogspot.com/_wWmWqyCEKEs/SyrLDr2gVsI/AAAAAAAAC1U/qCiICz4Dk6U/s1600-h/contentsyndication.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5416364766037825218" src="/blog/2009/12/content-syndication-seo-rel-canonical/image-0.png" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 149px;"/></a></p>
<p>With content syndication of our blog articles, there is a several day lag where Google treats our blog article as the duplicate content and returns the OSNews article in search results for a search similar to our the blog article’s title. After this lag time, the OSNews article is treated as duplicate content and our article is shown in the search results.</p>
<p><a href="http://4.bp.blogspot.com/_wWmWqyCEKEs/SyrLCz9MRnI/AAAAAAAAC1E/aKcg77ZqpPg/s1600-h/example1.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5416364751033484914" src="/blog/2009/12/content-syndication-seo-rel-canonical/image-0.png" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 244px;"/></a></p>
<p>During the lag time, a search for “google pages indexed seo”, an article I published last Thursday, the OSNews article is shown at search position #5.</p>
<p><a href="http://3.bp.blogspot.com/_wWmWqyCEKEs/SyrLDcTVJxI/AAAAAAAAC1M/cwtg7yjTgy8/s1600-h/example2.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5416364761863759634" src="/blog/2009/12/content-syndication-seo-rel-canonical/image-0.png" style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 238px;"/></a></p>
<p>After the lag time, a search for “google pages indexed seo” returned the original End Point blog article to search position #2.</p>
<p>Several other factors have influenced the lag time, but typically I’ve seen very similar behavior.</p>
<p>End Point’s content syndication has only been an issue with blog articles, since the majority of our new content comes in the form of blog articles. Examples of content syndication in the ecommerce space may include:</p>
<ul>
<li>inner-company content syndication of products across sister sites. For example, our client <a href="http://www.backcountry.com/">Backcountry.com</a> sells outdoor gear, while their site <a href="http://www.realcyclist.com/">RealCyclist</a> targets the road biking niche of the outdoor gear industry. Cycling products sold on both sites and may compete directly for search engine traffic.</li>
<li>syndication of product information through affiliate programs like <a href="http://www.cj.com/">Commission Junction</a> and <a href="http://www.avantlink.com/">AvantLink</a>. Affiliates are paid a small portion of the sales and may target traffic by building supplementary content or communities around content provided by ecommerce sites through the affiliate program.</li>
</ul>
<h3 id="cross-domain-relcanonical-tag">Cross-Domain rel=canonical Tag</h3>
<p>I’ve been planning to write this article and with impeccable timing, <a href="https://webmasters.googleblog.com/2009/12/handling-legitimate-cross-domain.html">Google announced support for the rel=canonical tag across different domains</a> this week. I’ve referenced the use of the rel=canonical tag in two articles (<a href="/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/">PubCon 2009 Takeaways</a>, <a href="/blog/2009/02/search-engine-optimization-thoughts/">Search Engine Thoughts</a>), but I haven’t gone too much into depth about its use. Support of the rel=canonical tag was introduced early this year as a method to help decrease duplicate content across a single domain. A non-canonical URL that includes this tag suggests its canonical URL to search engines. Search engines then use this suggestion in their algorithms and results to reduce the effects of duplicate content.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain"><link rel="canonical" href="http://www.example.com/product.php?item=swedish-fish" />
</code></pre></div><p>With the cross-domain rel=canonical support announcement, the rel=canonical tag presents another tool to battle duplicate content from content syndication across domains.</p>
<h3 id="back-to-content-syndication">Back to Content Syndication</h3>
<p>The point of my investigation was to identify whether or not content syndication to OSNews negatively affects our search traffic. The data above suggests that after the brief lag time, Google’s algorithm sorts out the source of the original content. The value of exposure, referral traffic, and link juice from OSNews outweighs lost search traffic during this lag time.</p>
<p>In the example of similar product content across backcountry.com’s sites, using the rel=canonical tag across domains would allow backcountry.com to suggest prioritization of same product URLs for search results. This may be a valuable tool for directing search traffic to the desired domain.</p>
<p>In the example of content syndication across sites that are not owned by the same company, the use of the rel=canonical tag is more complicated. If the goals of the site that grabs content are to compete directly for search traffic, they would likely not want to use the canonical tag. However, if the goal of the site that grabs content is to focus on search traffic from aggregate content or by building a community around the valuable content, they may be more willing to implement the cross-domain rel=canonical tag to point to the original source of the content. In the case of affiliate programs, I believe it will be difficult to negotiate the cross-domain rel=canonical tag use into existing or future contracts.</p>
<p>The takeaways:</p>
<ul>
<li>Content syndication of our blog does not cause negative long term effects on search. This should be monitored for sites that may have much different behavior than the data I provided above.</li>
<li>The announcement of support of the cross-domain rel=canonical tag may be helpful for battling duplicate content across sites, especially to sites owned by the same company.</li>
<li>The use of the cross-domain rel=canonical tag in affiliate programs or through sites owned by different companies will be trickier to negotiate.</li>
</ul>
List Google Pages Indexed for SEO: Two Step How Tohttps://www.endpointdev.com/blog/2009/12/google-pages-indexed-seo/2009-12-11T00:00:00+00:00Steph Skardal
<p>Whenever I work on SEO reports, I often start by looking at pages indexed in Google. I just want a simple list of the URLs indexed by the <em>GOOG</em>. I usually use this list to get a general idea of navigation, look for duplicate content, and examine initial counts of different types of pages indexed.</p>
<p>Yesterday, I finally got around to figuring out a command line solution to generate this desired indexation list. Here’s how to use the command line using <a href="http://www.endpoint.com/">http://www.endpoint.com/</a> as an example:</p>
<p><strong>Step 1</strong></p>
<p>Grab the search results using the “site:” operator and make sure you run an advanced search that shows 100 results. The URL will look something like: <a href="https://www.google.com/search?num=100&as_sitesearch=www.endpoint.com">https://www.google.com/search?num=100&as_sitesearch=www.endpoint.com</a></p>
<p>But it will likely have lots of other query parameters of lesser importance [to us]. Save the search results page as search.html.</p>
<img alt="" border="0" id="BLOGGER_PHOTO_ID_5414143053987661506" src="/blog/2009/12/google-pages-indexed-seo/image-0.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 750px;"/>
<p><strong>Step 2</strong></p>
<p>Run the following command:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash">sed <span style="color:#d20;background-color:#fff0f0">'s/<h3 class="r">/\n/g; s/class="l"/LINK\n/g'</span> search.html | grep LINK | sed <span style="color:#d20;background-color:#fff0f0">'s/<a href="\|" LINK//g'</span>
</code></pre></div><p>There you have it. Interestingly enough, the order of pages can be an indicator of which pages rank well. Typically, pages with higher PageRank will be near the top, although I have seen some strange exceptions. End Point’s indexed pages:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">http://www.endpoint.com/
http://www.endpoint.com/clients
http://www.endpoint.com/team
http://www.endpoint.com/services
http://www.endpoint.com/sitemap
http://www.endpoint.com/contact
http://www.endpoint.com/team/selena_deckelmann
http://www.endpoint.com/team/josh_tolley
http://www.endpoint.com/team/steph_powell
http://www.endpoint.com/team/ethan_rowe
http://www.endpoint.com/team/greg_sabino_mullane
http://www.endpoint.com/team/mark_johnson
http://www.endpoint.com/team/jeff_boes
http://www.endpoint.com/team/ron_phipps
http://www.endpoint.com/team/david_christensen
http://www.endpoint.com/team/carl_bailey
http://www.endpoint.com/services/spree
...
</code></pre></div><p>For the site I examined yesterday, I saved the pages as one.html, two.html, three.html and four.html because the site had about 350 results. I wrote a simple script to concatenate all the results:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-bash" data-lang="bash"><span style="color:#c00;font-weight:bold">#!/bin/bash
</span><span style="color:#c00;font-weight:bold"></span>
rm results.txt
<span style="color:#080;font-weight:bold">for</span> ARG in <span style="color:#369">$*</span>
<span style="color:#080;font-weight:bold">do</span>
sed <span style="color:#d20;background-color:#fff0f0">'s/<h3 class="r">/\n/g; s/class="l"/LINK\n/g'</span> <span style="color:#369">$ARG</span> | grep LINK | sed <span style="color:#d20;background-color:#fff0f0">'s/<a href="\|" LINK//g'</span> >> results.txt
<span style="color:#080;font-weight:bold">done</span>
</code></pre></div><p>And I called the script above with:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">./list_google_index.sh one.html two.html three.html four.html
</code></pre></div><p>This solution isn’t scalable nor is it particularly elegant. But it’s good for a quick and dirty list of pages indexed by the <em>GOOG</em>. I’ve worked with the WWW::Google::PageRank module before and there are restrictions on API request limits and frequency, so I would <strong>highly advise</strong> against writing a script that makes requests to Google repeatedly. I’ll likely use the script described above for sites with less than 1000 pages indexed. There may be other solutions out there to list pages indexed by Google, but as I said, I was going for a quick and dirty approach.</p>
<img alt="" border="0" id="BLOGGER_PHOTO_ID_5414146774384722018" src="/blog/2009/12/google-pages-indexed-seo/image-1.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 750px;"/>
<p>Remember not to get eaten by the Google Monster</p>
WordPress Plugin for Omniture SiteCatalysthttps://www.endpointdev.com/blog/2009/11/wordpress-plugin-for-omniture/2009-11-18T00:00:00+00:00Steph Skardal
<p>A couple of months ago, I integrated Omniture SiteCatalyst into an Interchange site for one of End Point’s clients, <a href="https://www.citypass.com/">CityPass</a>. Shortly after, the client added a blog to their site, which is a standalone WordPress instance that runs separately from the Interchange ecommerce application. I was asked to add SiteCatalyst tracking to the blog.</p>
<p>I’ve had some experience with WordPress plugin development, and I thought this was a great opportunity to develop a plugin to abstract the SiteCatalyst code from the WordPress theme. I was surprised that there were limited Omniture WordPress plugins available, so I’d like to share my experiences through a brief tutorial for building a WordPress plugin to integrate Omniture SiteCatalyst.</p>
<p>First, I created the base wordpress file to append the code near the footer of the wordpress theme. This file must live in the ~/wp-content/plugins/ directory. I named the file omniture.php.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php"> <?php <span style="color:#888">/*
</span><span style="color:#888"> Plugin Name: SiteCatalyst for WordPress
</span><span style="color:#888"> Plugin URI: https://www.endpointdev.com/
</span><span style="color:#888"> Version: 1.0
</span><span style="color:#888"> Author: Steph Powell
</span><span style="color:#888"> */</span>
<span style="color:#080;font-weight:bold">function</span> <span style="color:#06b;font-weight:bold">omniture_tag</span>() {
}
add_action(<span style="color:#d20;background-color:#fff0f0">'wp_footer'</span>, <span style="color:#d20;background-color:#fff0f0">'omniture_tag'</span>);
<span style="color:#c00;font-weight:bold">?></span><span style="color:#a61717;background-color:#e3d2d2">
</span></code></pre></div><p>In the code above, the wp_footer is a specific WordPress hook that runs just before the </body> tag. Next, I added the base Omniture code inside the omniture_tag function:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">...
<span style="color:#080;font-weight:bold">function</span> <span style="color:#06b;font-weight:bold">omniture_tag</span>() {
<span style="color:#c00;font-weight:bold">?></span><span style="color:#a61717;background-color:#e3d2d2">
</span><span style="color:#a61717;background-color:#e3d2d2"><script type="text/javascript">
</span><span style="color:#a61717;background-color:#e3d2d2"><!-- var s_account = 'omniture_account_id'; -->
</span><span style="color:#a61717;background-color:#e3d2d2"></script>
</span><span style="color:#a61717;background-color:#e3d2d2"><script type="text/javascript" src="/path/to/s_code.js"></script>
</span><span style="color:#a61717;background-color:#e3d2d2"><script type="text/javascript"><!--
</span><span style="color:#a61717;background-color:#e3d2d2">s.pageName='' //page name
</span><span style="color:#a61717;background-color:#e3d2d2">s.channel='' //channel
</span><span style="color:#a61717;background-color:#e3d2d2">s.pageType='' //page type
</span><span style="color:#a61717;background-color:#e3d2d2">s.prop1='' //traffic variable 1
</span><span style="color:#a61717;background-color:#e3d2d2">s.prop2='' //traffic variable 2
</span><span style="color:#a61717;background-color:#e3d2d2">s.prop3='' //traffic variable 3
</span><span style="color:#a61717;background-color:#e3d2d2">s.prop4= '' //traffic variable 4
</span><span style="color:#a61717;background-color:#e3d2d2">s.prop5= '' //traffic variable 5
</span><span style="color:#a61717;background-color:#e3d2d2">s.campaign= '' //campaign variable
</span><span style="color:#a61717;background-color:#e3d2d2">s.state= '' //user state
</span><span style="color:#a61717;background-color:#e3d2d2">s.zip= '' //user zip
</span><span style="color:#a61717;background-color:#e3d2d2">s.events= '' //user events
</span><span style="color:#a61717;background-color:#e3d2d2">s.products= '' //user products
</span><span style="color:#a61717;background-color:#e3d2d2">s.purchaseID= '' //purchase ID
</span><span style="color:#a61717;background-color:#e3d2d2">s.eVar1= '' //conversion variable 1
</span><span style="color:#a61717;background-color:#e3d2d2">s.eVar2= '' //conversion variable 2
</span><span style="color:#a61717;background-color:#e3d2d2">s.eVar3= '' //conversion variable 3
</span><span style="color:#a61717;background-color:#e3d2d2">s.eVar4= '' //conversion variable 4
</span><span style="color:#a61717;background-color:#e3d2d2">s.eVar5= '' //conversion variable 5
</span><span style="color:#a61717;background-color:#e3d2d2">/************* DO NOT ALTER ANYTHING BELOW THIS LINE ! **************/
</span><span style="color:#a61717;background-color:#e3d2d2">var s_code=s.t();if(s_code)document.write(s_code)
</span><span style="color:#a61717;background-color:#e3d2d2">--></script>
</span><span style="color:#a61717;background-color:#e3d2d2"><?php
</span><span style="color:#a61717;background-color:#e3d2d2">}
</span><span style="color:#a61717;background-color:#e3d2d2">
</span><span style="color:#a61717;background-color:#e3d2d2">...
</span></code></pre></div><p>To test the footer hook, I activated the plugin in the WordPress admin. A blog refresh should yield the Omniture code (with no variables defined) near the </body> tag of the source code.</p>
<p>After verifying that the code was correctly appended near the footer in the source code, I determined how to track the WordPress traffic in SiteCatalyst. For our client, the traffic was to be divided into the home page, static page, articles, tag pages, category pages and archive pages. The Omniture variables pageName, channel, pageType, prop1, prop2, and prop3 were modified to track these pages. Existing WordPress functions is_home, is_page, is_single, is_category, is_tag, is_month, the_title, get_the_category, the_title, single_cat_title, single_tag_title, the_date were used.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">...
<script type=<span style="color:#d20;background-color:#fff0f0">"text/javascript"</span>><!--
<?php
<span style="color:#080;font-weight:bold">if</span>(is_home()) { <span style="color:#888">//WordPress functionality to check if page is home page
</span><span style="color:#888"></span> <span style="color:#369">$pageName</span> = <span style="color:#369">$channel</span> = <span style="color:#369">$pageType</span> = <span style="color:#369">$prop1</span> = <span style="color:#d20;background-color:#fff0f0">'Blog Home'</span>;
} <span style="color:#080;font-weight:bold">elseif</span> (is_page()) { <span style="color:#888">//WordPress functionality to check if page is static page
</span><span style="color:#888"></span> <span style="color:#369">$pageName</span> = <span style="color:#369">$channel</span> = the_title(<span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#080;font-weight:bold">false</span>);
<span style="color:#369">$pageType</span> = <span style="color:#369">$prop1</span> = <span style="color:#d20;background-color:#fff0f0">'Static Page'</span>;
} <span style="color:#080;font-weight:bold">elseif</span> (is_single()) { <span style="color:#888">//WordPress functionality to check if page is article
</span><span style="color:#888"></span> <span style="color:#369">$categories</span> = get_the_category();
<span style="color:#369">$pageName</span> = <span style="color:#369">$prop2</span> = the_title(<span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#080;font-weight:bold">false</span>);
<span style="color:#369">$channel</span> = <span style="color:#369">$categories</span>[<span style="color:#00d;font-weight:bold">0</span>]-><span style="color:#369">name</span>;
<span style="color:#369">$pageType</span> = <span style="color:#369">$prop1</span> = <span style="color:#d20;background-color:#fff0f0">'Article'</span>;
} <span style="color:#080;font-weight:bold">elseif</span> (is_category()) { <span style="color:#888">//WordPress functionality to check if page is category page
</span><span style="color:#888"></span> <span style="color:#369">$pageName</span> = <span style="color:#369">$channel</span> = single_cat_title(<span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#080;font-weight:bold">false</span>);
<span style="color:#369">$pageName</span> = <span style="color:#d20;background-color:#fff0f0">'Category: '</span> . <span style="color:#369">$pageName</span>;
<span style="color:#369">$pageType</span> = <span style="color:#369">$prop1</span> = <span style="color:#d20;background-color:#fff0f0">'Category'</span>;
} <span style="color:#080;font-weight:bold">elseif</span> (is_tag()) { <span style="color:#888">//WordPress functionality to check if page is tag page
</span><span style="color:#888"></span> <span style="color:#369">$pageName</span> = <span style="color:#369">$channel</span> = single_tag_title(<span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#080;font-weight:bold">false</span>);
<span style="color:#369">$pageType</span> = <span style="color:#369">$prop1</span> = <span style="color:#d20;background-color:#fff0f0">'Tag'</span>;
} <span style="color:#080;font-weight:bold">elseif</span> (is_month()) { <span style="color:#888">//WordPress functionality to check if page is month page
</span><span style="color:#888"></span> <span style="color:#080;font-weight:bold">list</span>(<span style="color:#369">$month</span>, <span style="color:#369">$year</span>) = split(<span style="color:#d20;background-color:#fff0f0">' '</span>, the_date(<span style="color:#d20;background-color:#fff0f0">'F Y'</span>, <span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#d20;background-color:#fff0f0">''</span>, <span style="color:#080;font-weight:bold">false</span>));
<span style="color:#369">$pageName</span> = <span style="color:#d20;background-color:#fff0f0">'Month Archive: '</span> . <span style="color:#369">$month</span> . <span style="color:#d20;background-color:#fff0f0">' '</span> . <span style="color:#369">$year</span>;
<span style="color:#369">$channel</span> = <span style="color:#369">$pageType</span> = <span style="color:#369">$prop1</span> = <span style="color:#d20;background-color:#fff0f0">'Month Archive'</span>;
<span style="color:#369">$prop2</span> = <span style="color:#369">$year</span>;
<span style="color:#369">$prop3</span> = <span style="color:#369">$month</span>;
}
<span style="color:#080;font-weight:bold">echo</span> <span style="color:#d20;background-color:#fff0f0">"s.pageName = '</span><span style="color:#33b;background-color:#fff0f0">$pageName</span><span style="color:#d20;background-color:#fff0f0">' //page name</span><span style="color:#04d;background-color:#fff0f0">\n</span><span style="color:#d20;background-color:#fff0f0">"</span>;
<span style="color:#080;font-weight:bold">echo</span> <span style="color:#d20;background-color:#fff0f0">"s.channel = '</span><span style="color:#33b;background-color:#fff0f0">$channel</span><span style="color:#d20;background-color:#fff0f0">' //channel</span><span style="color:#04d;background-color:#fff0f0">\n</span><span style="color:#d20;background-color:#fff0f0">"</span>;
<span style="color:#080;font-weight:bold">echo</span> <span style="color:#d20;background-color:#fff0f0">"s.pageType = '</span><span style="color:#33b;background-color:#fff0f0">$pageType</span><span style="color:#d20;background-color:#fff0f0">' //page type</span><span style="color:#04d;background-color:#fff0f0">\n</span><span style="color:#d20;background-color:#fff0f0">"</span>;
<span style="color:#080;font-weight:bold">echo</span> <span style="color:#d20;background-color:#fff0f0">"s.prop1 = '</span><span style="color:#33b;background-color:#fff0f0">$prop1</span><span style="color:#d20;background-color:#fff0f0">' //traffic variable 1</span><span style="color:#04d;background-color:#fff0f0">\n</span><span style="color:#d20;background-color:#fff0f0">"</span>;
<span style="color:#080;font-weight:bold">echo</span> <span style="color:#d20;background-color:#fff0f0">"s.prop2 = '</span><span style="color:#33b;background-color:#fff0f0">$prop2</span><span style="color:#d20;background-color:#fff0f0">' //traffic variable 2</span><span style="color:#04d;background-color:#fff0f0">\n</span><span style="color:#d20;background-color:#fff0f0">"</span>;
<span style="color:#080;font-weight:bold">echo</span> <span style="color:#d20;background-color:#fff0f0">"s.prop3 = '</span><span style="color:#33b;background-color:#fff0f0">$prop3</span><span style="color:#d20;background-color:#fff0f0">' //traffic variable 3</span><span style="color:#04d;background-color:#fff0f0">\n</span><span style="color:#d20;background-color:#fff0f0">"</span>;
<span style="color:#c00;font-weight:bold">?></span><span style="color:#a61717;background-color:#e3d2d2">
</span><span style="color:#a61717;background-color:#e3d2d2">s.prop4 = '' //traffic variable 4
</span><span style="color:#a61717;background-color:#e3d2d2">
</span><span style="color:#a61717;background-color:#e3d2d2">...
</span></code></pre></div><p>The plugin allows you to freely switch between WordPress themes without having to manage the SiteCatalyst code and to track the basic WordPress page hierarchy. Here are example outputs of the SiteCatalyst variables broken down by page type:</p>
<h3 id="homepage">Homepage</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">s.pageName = <span style="color:#d20;background-color:#fff0f0">'Blog Home'</span> <span style="color:#888">//page name
</span><span style="color:#888"></span>s.channel = <span style="color:#d20;background-color:#fff0f0">'Blog Home'</span> <span style="color:#888">//channel
</span><span style="color:#888"></span>s.pageType = <span style="color:#d20;background-color:#fff0f0">'Blog Home'</span> <span style="color:#888">//page type
</span><span style="color:#888"></span>s.prop1 = <span style="color:#d20;background-color:#fff0f0">'Blog Home'</span> <span style="color:#888">//traffic variable 1
</span><span style="color:#888"></span>s.prop2 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 2
</span><span style="color:#888"></span>s.prop3 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 3
</span></code></pre></div><h3 id="tag-page">Tag Page</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">s.pageName = <span style="color:#d20;background-color:#fff0f0">'chocolate'</span> <span style="color:#888">//page name
</span><span style="color:#888"></span>s.channel = <span style="color:#d20;background-color:#fff0f0">'chocolate'</span> <span style="color:#888">//channel
</span><span style="color:#888"></span>s.pageType = <span style="color:#d20;background-color:#fff0f0">'Tag'</span> <span style="color:#888">//page type
</span><span style="color:#888"></span>s.prop1 = <span style="color:#d20;background-color:#fff0f0">'Tag'</span> <span style="color:#888">//traffic variable 1
</span><span style="color:#888"></span>s.prop2 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 2
</span><span style="color:#888"></span>s.prop3 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 3
</span></code></pre></div><h3 id="category-page">Category Page</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">s.pageName = <span style="color:#d20;background-color:#fff0f0">'Category: Food'</span> <span style="color:#888">//page name
</span><span style="color:#888"></span>s.channel = <span style="color:#d20;background-color:#fff0f0">'Food'</span> <span style="color:#888">//channel
</span><span style="color:#888"></span>s.pageType = <span style="color:#d20;background-color:#fff0f0">'Category'</span> <span style="color:#888">//page type
</span><span style="color:#888"></span>s.prop1 = <span style="color:#d20;background-color:#fff0f0">'Category'</span> <span style="color:#888">//traffic variable 1
</span><span style="color:#888"></span>s.prop2 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 2
</span><span style="color:#888"></span>s.prop3 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 3
</span></code></pre></div><h3 id="static-page">Static Page</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">s.pageName = <span style="color:#d20;background-color:#fff0f0">'About'</span> <span style="color:#888">//page name
</span><span style="color:#888"></span>s.channel = <span style="color:#d20;background-color:#fff0f0">'About'</span> <span style="color:#888">//channel
</span><span style="color:#888"></span>s.pageType = <span style="color:#d20;background-color:#fff0f0">'Static Page'</span> <span style="color:#888">//page type
</span><span style="color:#888"></span>s.prop1 = <span style="color:#d20;background-color:#fff0f0">'Static Page'</span> <span style="color:#888">//traffic variable 1
</span><span style="color:#888"></span>s.prop2 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 2
</span><span style="color:#888"></span>s.prop3 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 3
</span></code></pre></div><h3 id="archive">Archive</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">s.pageName = <span style="color:#d20;background-color:#fff0f0">'Month Archive: November 2009'</span> <span style="color:#888">//page name
</span><span style="color:#888"></span>s.channel = <span style="color:#d20;background-color:#fff0f0">'Month Archive'</span> <span style="color:#888">//channel
</span><span style="color:#888"></span>s.pageType = <span style="color:#d20;background-color:#fff0f0">'Month Archive'</span> <span style="color:#888">//page type
</span><span style="color:#888"></span>s.prop1 = <span style="color:#d20;background-color:#fff0f0">'Month Archive'</span> <span style="color:#888">//traffic variable 1
</span><span style="color:#888"></span>s.prop2 = <span style="color:#d20;background-color:#fff0f0">'2009'</span> <span style="color:#888">//traffic variable 2
</span><span style="color:#888"></span>s.prop3 = <span style="color:#d20;background-color:#fff0f0">'November'</span> <span style="color:#888">//traffic variable 3
</span></code></pre></div><h3 id="article">Article</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-php" data-lang="php">s.pageName = <span style="color:#d20;background-color:#fff0f0">'Hello world!'</span> <span style="color:#888">//page name
</span><span style="color:#888"></span>s.channel = <span style="color:#d20;background-color:#fff0f0">'Test Category'</span> <span style="color:#888">//channel
</span><span style="color:#888"></span>s.pageType = <span style="color:#d20;background-color:#fff0f0">'Article'</span> <span style="color:#888">//page type
</span><span style="color:#888"></span>s.prop1 = <span style="color:#d20;background-color:#fff0f0">'Article'</span> <span style="color:#888">//traffic variable 1
</span><span style="color:#888"></span>s.prop2 = <span style="color:#d20;background-color:#fff0f0">'Hello world!'</span> <span style="color:#888">//traffic variable 2
</span><span style="color:#888"></span>s.prop3 = <span style="color:#d20;background-color:#fff0f0">''</span> <span style="color:#888">//traffic variable 3
</span></code></pre></div><p>A followup step to this plugin would be to use the wp_options table in WordPress to manage the Omniture account id, which would allow admin to set the Omniture account id through the WordPress admin without editing the plugin code. I’ve uploaded the plugin to a GitHub repository <a href="https://github.com/stephskardal/wordpress-sitecatalyst/">here</a>.</p>
<p><em>Update: This plugin is included in the WordPress plugin registry and can be found at <a href="https://wordpress.org/extend/plugins/omniture-sitecatalyst-tracking/">https://wordpress.org/extend/plugins/omniture-sitecatalyst-tracking/</a>.</em></p>
PubCon Vegas: 7 Takeaway Nuggetshttps://www.endpointdev.com/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/2009-11-16T00:00:00+00:00Steph Skardal
<p>I’m back at work after last week’s <a href="https://www.pubcon.com/">PubCon Vegas</a>. I published several articles about specific sessions, but I wanted to provide some nuggets on recurring themes of the conference.</p>
<p><a href="/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/image-0-big.jpeg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5404762489849624482" src="/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/image-0.jpeg" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 300px;"/></a></p>
<h4 id="google-caffeine-update">Google Caffeine Update</h4>
<p>This year Google rolled out some changes referred to as the <a href="https://mashable.com/2009/08/10/google-caffeine/">Google Caffeine</a> update. This change increases the speed and size of the index, moves Google search to real-time, and improves search results relevancy and accuracy. It was a popular topic at the conference, however, not much light was shed on how algorithm changes would affect your search results, if at all. I’ll have to keep an eye on this to see if there are any significant changes in End Point’s search performance.</p>
<h4 id="bing">Bing</h4>
<p>Bing is gaining traction. They want to get [at least] 51% of the search market share.</p>
<p><a href="/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/image-1-big.jpeg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5404762491452538162" src="/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/image-1.jpeg" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 208px;"/></a></p>
<h4 id="social-media">Social media</h4>
<p>Social media was a hot topic at the conference. An entire track was allocated to Twitter topics on the first day of the conference. However, it still pales in comparison to search. Of all referrals on the web, search still accounts for 98% and social media referrals only account for less than 1% (<a href="https://web.archive.org/web/20091129014325/http://chitika.com/research/2009/social-vs-search/">view referral data here</a>). Dr. Pete from SEOmoz nicely summarized the <a href="https://www.seomoz.org/blog/is-social-media-roi-unmeasurable">elephant in the room</a> at PubCon regarding social media that it’s important to measure social media response to determine if it provides business value.</p>
<h4 id="ecommerce-advice">Ecommerce Advice</h4>
<p>I asked <a href="https://www.robsnell.com/">Rob Snell</a>, author of <em>Starting a Yahoo Business for Dummies</em>, for the most important advice for ecommerce SEO he could provide. He explained the importance of content development and link building to target keywords based on keyword conversion. Basically, SEO efforts shouldn’t be wasted on keywords that don’t convert well. I typically don’t have access to client keyword conversion data, but this is great advice.</p>
<h4 id="internal-seo-processes">Internal SEO Processes</h4>
<p>Another recurring topic I observed at PubCon was that often internal SEO processes are a much bigger obstacle than the actual SEO work. It’s important to get the entire team on your side. Alex Bennert of Wall Street Journal discussed understanding your audience when presenting SEO. Here are some examples of appropriate topics for a given audience:</p>
<ul>
<li>IT Folks: sitemaps, duplicate content (parameter issues, pagination, sorting, crawl allocation, dev servers), canonical link elements, 301 redirects, intuitive link structure</li>
<li>Biz Dev & Marketing Folks: syndication of content, evaluation of vendor products & integration, assessing SEO value and link equity of partner sites, microsites, leveraging multiple assets</li>
<li>Content Developers: on page elements best practices, linking, anchor text best practices, keyword research, keyword trends, analytics</li>
<li>Management: progress, timelines, roadmaps</li>
</ul>
<p>On the topic of internal processes, I was entertained by the various comments expressing the developer-marketer relationship, for example:</p>
<ul>
<li>“Don’t ever let a developer control your URL structure.”</li>
<li>“Don’t ever let a developer control your site architecture.”</li>
<li>“This site looks like it was designed by a developer.”</li>
</ul>
<p>Apparently developers are the most obvious scapegoat. Back to the point, though: It often requires more effort to get SEO understanding and support than actually explaining what needs to be done.</p>
<h4 id="search-engine-spam">Search Engine Spam</h4>
<p>Search engine spam detection is cool. During a couple of sessions with <a href="https://www.mattcutts.com/">Matt Cutts</a>, I became interested in writing code to detect search spam. For example:</p>
<ul>
<li>Crawling the web to detect links where the anchor text is ‘.’.</li>
<li>Crawling the web to identify sites where robots.txt blocks ia_archiver.</li>
<li>Crawling the web to detect pages with keyword stuffing.</li>
</ul>
<p>I’ve typically been involved in the technical side of SEO (duplicate content, indexation, crawlability), and haven’t been involved in link building or content development, but these discussions provoked me to start looking at search spam from an engineer’s perspective.</p>
<h4 id="google-parameter-masking">Google Parameter Masking</h4>
<p>Apparently I missed the announcement of parameter masking in Google Webmaster Tools. I’ve helped battle duplicate content for several clients, and at PubCon I heard about parameter masking provided in Google Webmaster Tools. This functionality was announced in October of 2009 and allows you to provide suggestions to the crawler to ignore specific query parameters.</p>
<p>Parameter masking is yet another solution to managing duplicate content in addition to the rel=“canonical” tag, creative uses of robots.txt, and the nofollow tag. The ideal solution for SEO would be to build a site architecture that doesn’t require the use of any of these solutions. However, as developers we have all experienced how legacy code persists and sometimes a low effort-high return solution is the best short term option.</p>
<p><a href="/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/image-2-big.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5404762496685302306" src="/blog/2009/11/pubcon-vegas-7-takeaway-nuggets/image-2.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 277px;"/></a></p>
PubCon Vegas Day 3: User Generated Contenthttps://www.endpointdev.com/blog/2009/11/pubcon-vegas-day-3-user-generated/2009-11-13T00:00:00+00:00Steph Skardal
<p>On day 3 of <a href="https://www.pubcon.com/">PubCon Vegas</a>, a great session I attended was <a href="https://www.pubcon.com/session-details?action=view&conference=pubcon85&record=164">Optimizing Forums For Search & Dealing with User Generated Content</a> with Dustin Woodard, Lawrence Coburn, and Roger Dooley. User generated content is content generated by users in the form of message boards, customizable profiles, forums, reviews, wikis, blogs, article submission, question and answer, video media, or social networks.</p>
<p>Some good statistics were presented about why to tap into user generated content. Nielsen research recently released showed that 1 out of every 11 minutes spent online is on a social network and 2/3rds of customer “touch points” are user-generated.</p>
<p>Dustin provided some interesting details about long tail traffic. He looked at HitWise’s data of the top 10,000 search terms for a 3 month period. The top 100 terms accounted for 5.7% of all traffic, the top 1000 terms accounted for 10.6% of all traffic, and the entire 10,000 data set accounted for just 18.5% of all traffic. With this data, representing the long tail would be analogous to a lizard with a one inch head and a tail that was 221 miles long that represents the long tail traffic.</p>
<p><a href="https://2.bp.blogspot.com/_wWmWqyCEKEs/Sv22ffApe0I/AAAAAAAACrU/5ht8OdMVoac/s1600-h/Zebra-Tailed-Lizard.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5403675779930880834" src="/blog/2009/11/pubcon-vegas-day-3-user-generated/image-0.jpeg" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 214px;"/></a></p>
<p>Dustin gave the following steps for developing a user generated content community:</p>
<ol>
<li>Seed it with a few editors and really good initial content.</li>
<li>Give them a voice.</li>
<li>Make it easy to contribute.</li>
<li>Make it cool or trendy.</li>
<li>Provide ownership.</li>
<li>Create competition with contests, ranking or by highlighting expertise.</li>
<li>Build a sense of community or a sense of exclusivity.</li>
<li>Give the people community a purpose.</li>
</ol>
<p>All SEO best practices apply to a user generated content, but throughout the session, I learned several specific user generated content tips:</p>
<ul>
<li>Predefining keyword rich categories, topics and tags will go a long way with optimization. The better structure for topics that is created up front, the better the user generated content can content in the long run. Users are not inherently good at content organization, so content can be easily buried with poor information architecture.</li>
<li>Developing automated cross-linking between user generated content helps improve authority, build clusters of content, and enrich the internal link structure. Dustin had experience with building widgets to automatically links to 5 pieces of user generated content and another widget to allow the user to select several pieces of user generated content from a set of related content.</li>
<li>Examples of battling duplicate content include disallowing duplicate page titles and meta descriptions. Content that is moved, renamed or deleted should be managed well.</li>
<li>Finally, building a badge or widget to display user involvement helps increase external linking to your site, but this should be carefully managed to avoid appearing spammy. Widget best practices are that the widget should have excellent accessibility, widgets should be simple with light branding and always have fresh content.</li>
<li>Developing your own tiny URL helps pass and keep intact external links to your site with user generated content. Lawrence suggested to “gently tweet” user generated content that is the highest quality.</li>
</ul>
<p>Several of End Point’s clients are either in the middle of or considering building a community with user generated content. In ecommerce, blogs, forums, reviews, and Q&A are the most prevalent types of user generated content that I’ve encountered. Many of the things mentioned in this session were good tips to consider throughout the development of user generated content for ecommerce.</p>
PubCon Vegas Day 2: International and Mega Site SEO, and Tools for SEOhttps://www.endpointdev.com/blog/2009/11/pubcon-vegas-day-2-international-and/2009-11-12T00:00:00+00:00Steph Skardal
<p>On the second day of <a href="https://www.pubcon.com/">PubCon Vegas</a>, I attended several SEO track sessions including “SEO for Ecommerce”, “International and European Site Optimization”, “Mega Site SEO”, and “SEO/SEM Tools”. A mini-summary of several of the sessions is presented below.</p>
<p>Derrick Wheeler from Microsoft.com spoke on <a href="https://www.pubcon.com/session-details?action=view&conference=pubcon85&record=116">Mega Site SEO</a> about “taming the beast”. Microsoft has 1.2 billion URLs that are comprised of thousands of web properties. For mega site SEO, Derrick highlighted:</p>
<ul>
<li>Content is NOT king. Structure is! Content is like the princess-in-waiting after structure has been mastered.</li>
<li>Developing an overall SEO approach and organization to getting structure, content, and authority SEO completed is more valuable or relevant to the actual SEO work. This was a common theme among many of the presentations at PubCon.</li>
<li>Getting metrics set up at the beginning of SEO work is a very important step to measure and justify progress.</li>
<li>Don’t be afraid to say no to low priority items.</li>
</ul>
<p>Most developers deal with a large amount of legacy code. Derrick discussed primary issues when working with legacy problems:</p>
<ul>
<li>Duplicate and undesirable pages. For Microsoft.com, managing and dealing with 1.2 billion pages results in a lot of duplicate and undesirable pages from the past.</li>
<li>Multiple redirects.</li>
<li>Improper error handling (error handling on 404s or 500s).</li>
<li>International URL structure can be a problem for international sites. Having an appropriate TLD (top level domain) is the best solution, but if that’s not possible, a process should be implemented to regulate the international urls.</li>
<li>Low Quality Page Titles and Meta Tags. For large sites with hundreds of thousands of pages, it’s really important to have unique page titles and meta descriptions or to have a template that forces uniqueness.</li>
</ul>
<p>In summary, <strong>structure</strong> and <strong>internal processes</strong> are areas to focus on for Mega Site SEO. Legacy problems are something to be aware of when you have a site so large where changes won’t be implemented as quickly as small site changes.</p>
<p>In <a href="https://www.pubcon.com/session-details?action=view&conference=pubcon85&record=172">International and European Search Management</a>, Michael Bonfils, Nelson James, and Andy Atkins-Krueger discussed international SEO and SEM tactics. Takeaways include:</p>
<ul>
<li>In terms of international search marketing, it’s important to incorporate culture into search optimization and marketing. If it works in one country, it may not work in another country and so don’t offend a culture by not understanding it. Some examples of content differences for targeting different cultures include emphasizing price points, focusing on product quality, and asserting authority or trust on a site.</li>
<li>It’s also important to understand how linguistics affects your keyword marketing. Automatic translation should not be used (all the speakers mentioned this). A good example of linguistics and search targeting is the use of the search term “soccer cleats”, or “football boots”. In England, the term “football boot” has a very small portion of the traffic share, but singular terms in other languages (“scarpe de calcio”, “botas de futbal”) have a much larger percentage of the search market share. Andy shared many other examples of how direct translation would not be the best keywords to target (“car insurance”, “healthcare”, “30% off”, “cheap flights”).</li>
<li>Local hosting is important for metrics, linking, and to develop trust. Nelson James shared research that shows that 80% of the top 10 results of the top 30 keywords in china had a ‘.cn’ top level domain, but the other top sites that were ‘.com’ sites are all hosted in china.</li>
<li>Other technical areas for international search that were mentioned are using the meta language tag, pinyin, charset, and language set. Duplicate content also will become a problem across sites of the same language.</li>
<li>It’s important to understand the search market share. In Russia, Google shares 35% of the search market and Yandexx has 54%. In China, Baidu has 76% and Google has 22%. There are some reasons that explain these market share differences. Yandexx was written to manage the large Russian vocabulary that Google does not handle as well. Baidu handles search for media better than Google and search traffic in China is much more entertainment driven rather than business driven in the US.</li>
</ul>
<p>In the last session of the day, about 100 tools were discussed in <a href="https://www.pubcon.com/session-details?action=view&conference=pubcon85&record=210">SEO/SEM Tools</a>. I’m planning on writing another blog post with a summary of these tools, but here’s a short list of the tools mentioned by multiple speakers:</p>
<ul>
<li>SEMRush</li>
<li>Google: Keyword Ad Tool, Webmaster Tools, Adplanner, SocialGraph API, Google Trends, Analytics, Google Insights</li>
<li>SpyFu: Kombat, Domain Ad History, Smart Search, Keyword Ad History</li>
<li>SEOBook</li>
<li>SEOmoz: Linkscape, Mozbar, Top Pages, etc.</li>
<li>MajesticSEO</li>
<li>Raven SEO Tools: Website Analytics, Campaign Reports</li>
</ul>
<p>Stay tuned for a day 3 and wrap up article!</p>
PubCon Vegas Day 1: Keyword Research Sessionhttps://www.endpointdev.com/blog/2009/11/pubcon-vegas-keyword-research-session/2009-11-11T00:00:00+00:00Steph Skardal
<p>On the first day of <a href="https://www.pubcon.com/">PubCon Vegas</a>, I was bombarded by information, sessions, and people. PubCon is a SEO/SEM conference that has a variety of sessions categorized in SEO (Search Engine Optimization), SEM (Search Marketing), Social Media and Affiliates. My primary interest is in SEO, which is why I attended the SEO track yesterday that included sessions about in-house SEO, organic keyword research and selection, and hot topics in SEO.</p>
<p>Because my specific involvement in SEO has focused on technical SEO, I was surprised that my highlight of day one was <a href="https://www.pubcon.com/session-details?action=view&conference=pubcon85&record=180">“Smart Organic Keyword Research and Selection”</a> which included speakers Wil Reynolds, Craig Paddock, Carolyn Shelby, and Mark Jackson.</p>
<p>With good organization and humor, Carolyn first presented the “ABCs of Organic Keyword Research and Selection”: A is for analytics and knowing your audience. B is for brainstorm and bonus. and C is for <strong>Cookie!</strong>, crunch the numbers, cull the lists, and create a final list of keywords.</p>
<p>On the analytics side, Carolyn mentioned good sources of analytics include web server logs (<a href="/blog/2009/03/end-point-search-engine-bot-parsing/">read my article on the value of log or bot parsing</a>), Google Analytics “traffic generating” keyword list, and logs from internal site search.</p>
<p>In regards to knowing your audience, Carolyn shared her personal experience of focus group research: For a project that targeted teenage girls, she invited her daughter and several of her daughter’s friends to join her around the table with laptops. She showed them a picture and ask them to search for that image. She recorded the search terms used and used this information to help understand her target audience behavior.</p>
<p>On the brainstorm side, she likes to involve core web team members, product managers, marketing, developers, designers, promoters, marketers, and front liners (customer service representatives, tech support). B was also for bonus, which was to get input from the “suits” of a company to get a list of ideal keywords to understand how they measure keyword success.</p>
<p>Craig Paddock spoke on “Organic Keyword Research and Selection” next. He touched on some of the following SEO keyphrase concepts:</p>
<ul>
<li><strong>keyphrase research</strong>: Keyword research is based on keyword popularity, click through rate, quality (measured by conversion and engagement), keyword competitiveness, and current ranking</li>
<li><strong>keyphrase expanders and variations</strong>: Broad keyword phrases should include variations of keywords that include words like ‘best’, ‘online’, ‘buy’, ‘cheap’, ‘discount’, ‘wholesale’, ‘accessories’, ‘supplies’, ‘reviews’, and abbreviations of words like states. For End Point’s ecommerce clients, targeting keyphrases with customer reviews is a great way to generate traffic from user generated content</li>
<li><strong>keyphrase discovery</strong>: It shouldn’t be assumed that clients know the industry. Craig shared an example that his boxing retailer client made the mistake of targeting specific boxing terms that had low traffic. They expanded to include more popular terms like “lose weight” and “burn calories”. Another tactic to discover keyphrase is to ask what kind of problems the website service offered solve and choose keywords that target these questions and answers.</li>
<li><strong>keyphrase quality</strong>: Keyphrase quality is typically measured by conversion rate (revenue / visitor) or engagement. Engagement is measured by the time on site, pages/visit, and bounce rate, which are commonly included in analytics packages.</li>
<li><strong>keyphrase selection</strong>: Using exact match and broad match on keywords is helpful and let the customers guide the keyword selection. Craig mentioned that data shows that there is a higher conversion rate on more specific keyphrases, which isn’t surprising.</li>
<li><strong>keyphrase targeting</strong>: Keyphrase targeting should match competitiveness with link popularity. An example of this being that more competitive words on your site should be higher up in the hierarchy of the site such as on the home page. For End Point, this would involve us targeting competitive phrases terms like “ecommerce”, “ruby on rails development”, and “web application development” on our homepage and targeting less competitive phrases such as “interchange development” or “ruby on rails ecommerce” on pages lower in the hierarchy.</li>
<li><strong>keyphrase analysis</strong>: One area of interest was how analytics tools attribute “credit” to keyphrases. In Google Analytics, if a customer searches “interchange consulting” and visits endpoint.com, then a week later searches “end point”, the conversion or credit of the keyphrase is attributed to the “end point” keyword rather than “interchange consulting”. This is important in ecommerce because this attribution doesn’t accurately credit targeted keywords for revenue. Craig did mention that other tools (including Omniture) provide the ability to select last click attribute versus first click attribute to fix this attribution problem. Another solution to this problem mentioned was to set a user defined variable in Google Analytics equal to a cookie that has the first click search term (“interchange consulting” in the example above) and set the cookie to not expire.</li>
</ul>
<p>Wil Reynolds spoke next on “Keyword Analysis AFTER the rankings”. He touched on an important concept that SEO (specifically keyphrase research and targeting) is never <strong>done</strong> because keywords are constantly evolving because people change the way they search, blended search (video, image) is on the rise, and there are social or economic influences on the keyword popularity. Some good examples of keyphrase trending include:</p>
<ul>
<li>“Shopping” was a good keyword in 1999 because ecommerce was growing on the web and users didn’t know what to search for.</li>
<li>“Handheld device” transitioned to “Smartphone”</li>
<li>“Eco-Friendly” has grown while “Environmentally Friendly” has declined — <a href="http://www.google.com/insights/search/#q=eco%20friendly%2Cenvironmentally%20friendly">view this trend here</a></li>
<li>“Netbooks” and “Ultraportables” are popular search terms on the rise that were non-existent two years ago — <a href="http://www.google.com/insights/search/#q=netbooks">view netbook trends here</a></li>
<li>Brands in the gear industry evolve at a much faster pace than the plumbing or wood floor industry</li>
</ul>
<p>Wil’s examples and advice apply directly to our clients who should to be aware of social and economic influences that may require they change they keyphrase targeting over time.</p>
<p>Finally, Mark Jackson spoke on focusing your keywords for better results. He discussed the importance of analyzing the keyword competitiveness to determine which keywords to target to get the most value out of keyword SEO work.</p>
<p>In summary, I <strong>still</strong> don’t love keyword and keyphrase research and selection :), but I found that the speakers presented a great overview of keyword research and selection with a good mixture of personal experience, expertise and examples. In summary, some great concepts to keep in mind in regards to keyword research are:</p>
<ul>
<li>There are always missed opportunities in keyword targeting.</li>
<li>There are lotsa tools! Tools are good for measuring keyphrase competitiveness, user engagement, and identifying missed opportunities.</li>
<li>SEO keyphrase research and selection is an ongoing process.</li>
</ul>
<p>Now, back to day 2 activities…</p>
Performance optimization of icdevgroup.orghttps://www.endpointdev.com/blog/2009/10/performance-optimization-of/2009-10-23T00:00:00+00:00Jon Jensen
<p>Some years ago Davor Ocelić redesigned <a href="http://www.icdevgroup.org/">icdevgroup.org</a>, Interchange’s home on the web. Since then, most of the attention paid to it has been on content such as news, documentation, release information, and so on. We haven’t looked much at implementation or optimization details. Recently I decided to do just that.</p>
<p><strong>Interchange optimizations</strong></p>
<p>There is currently no separate logged-in user area of icdevgroup.org, so Interchange is primarily used here as a templating system and database interface. The automatic read/write of a server-side user session is thus unneeded overhead, as is periodic culling of the old sessions. So I turned off permanent sessions by making all visitors appear to be search bots. Adding to interchange.cfg:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">RobotUA *
</code></pre></div><p>That would not work for most Interchange sites, which need a server-side session for storing mv_click action code, scratch variables, logged-in state, shopping cart, etc. But for a read-only content site, it works well.</p>
<p>By default, Interchange writes user page requests to a special tracking log as part of its UserTrack facility. It also outputs an X-Track HTTP response header with some information about the visit which can be used by a (to my knowledge) long defunct analytics package. Since we don’t need either of those features, we can save a tiny bit of overhead. Adding to catalog.cfg:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">UserTrack No
</code></pre></div><p>Very few Interchange sites have any need for UserTrack anymore, so this is commonly a safe optimization to make.</p>
<p><strong>HTTP optimizations</strong></p>
<p>Today I ran the excellent webpagetest.org test, and this was the <a href="http://www.webpagetest.org/result/091023_2M8V/">icdevgroup.org test result</a>. Even though icdevgroup.org is a fairly simple site without much bloat, two obvious areas for improvement stood out.</p>
<p>First, gzip/deflate compression of textual content should be enabled. That cuts down on bandwidth used and page delivery time by a significant amount, and with modern CPUs adds no appreciable extra CPU load on either the client or the server.</p>
<p>We’re hosting icdevgroup.org on Debian GNU/Linux with Apache 2.2, which has a reasonable default configuration of mod_deflate that does this, so it’s easy to enable:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">a2enmod deflate
</code></pre></div><p>That sets up symbolic links in /etc/apache2/mods-enabled for deflate.load and deflate.conf to enable mod_deflate. (Use a2dismod to remove them if needed.)</p>
<p>I added two content types for CSS & JavaScript to the default in deflate.conf:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/x-javascript
</code></pre></div><p>That used to be riskier when very old browsers such as Netscape 3 and 4 claimed to support compressed CSS & JavaScript but actually didn’t. But those browsers are long gone.</p>
<p>The next easy optimization is to enable proxy and browser caching of static content: images, CSS, and JavaScript files. By doing this we eliminate all HTTP requests for these files; the browser won’t even check with the server to see if it has the current version of these files once it has loaded them into its cache, making subsequent use of those files blazingly fast.</p>
<p>There is, of course, a tradeoff to this. Once the browser has the file cached, you can’t make it fetch a newer version unless you change the filename. So we’ll set a cache lifetime of only one hour. That’s long enough to easily cover most users' browsing sessions at a site like this, but short enough that if we need to publish a new version of one of these files, it will still propagate fairly quickly.</p>
<p>So I added to the Apache configuration file for this virtual host:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">ExpiresActive On
ExpiresByType image/gif "access plus 1 hour"
ExpiresByType image/jpeg "access plus 1 hour"
ExpiresByType image/png "access plus 1 hour"
ExpiresByType text/css "access plus 1 hour"
ExpiresByType application/x-javascript "access plus 1 hour"
FileETag None
Header unset ETag
</code></pre></div><p>This adds the HTTP response header “Cache-Control: max-age=3600” for those static files. I also have Apache remove the ETag header which is not needed given this caching and the Last-modified header.</p>
<p>There are cases where the above configuration would be too broad, for example, if you have:</p>
<ul>
<li>images that differ with the same filename, such as CAPTCHAs</li>
<li>static files that vary based on logged-in state</li>
<li>dynamically-generated CSS or JavaScript files with the same name</li>
</ul>
<p>If the website is completely static, including the HTML, or identical for all users at the same time even though dynamically generated, we could also enable caching the HTML pages themselves. But in the case of icdevgroup.org, that would probably cause trouble with the Gitweb repository browser, live documentation searches, etc.</p>
<p>After those changes, we can see the <a href="http://www.webpagetest.org/result/091023_2M91/">results of a new webpagetest.org run</a> and see that we reduced the bytes transferred, and the delivery time. It’s especially dramatic to see how much faster subsequent page views of the Hall of Fame are, since it has many screenshot thumbnail images.</p>
<p>Optimizing a simple non-commerce site such as icdevgroup.org is easy and even fun. With caution and practicing on a non-production system, complex ecommerce sites can be optimized using the same techniques, with even more dramatic benefits.</p>
SEO: External Links and PageRankhttps://www.endpointdev.com/blog/2009/09/seo-external-links-and-pagerank/2009-09-17T00:00:00+00:00Steph Skardal
<p>I had a flash of inspiration to write an article about external links in the world of search engine optimization. I’ve created many SEO reports for End Point’s clients with an emphasis on technical aspects of search engine optimization. However, at the end of the SEO report, I always like to point out that search engine performance is dependent on having high quality fresh and relevant content and popularity (for example, PageRank). The number of external links to a site is a large factor in popularity of a site, and so the number of external links to a site can positively influence search engine performance.</p>
<p>After wrapping up a report yesterday, I wondered if the external link data that I provide to our clients is meaningful to them. What is the average response when I report, “You should get high quality external links from many diverse domains”?</p>
<p>So, I investigated some data of well known and less well known sites to display a spectrum of external link and PageRank data. Here is the origin of some of the less well known domains referenced in the data below:</p>
<ul>
<li>www.petfinder.com: This is where my dogs came from.</li>
<li>www.endpoint.com: That’s Us!</li>
<li>www.sonypictures.com/movies/district9/: The site for the movie District 9 — I saw it last weekend.</li>
<li>marketstreetgrill.com: Market Street Grill is a great seafood restaurant in Salt Lake City.</li>
<li>divascupcakes.com: This is a great gourmet cupcake place in Salt Lake City.</li>
<li>rediguana.com: A great Mexican food restaurant in Salt Lake City.</li>
</ul>
<p>And here is the data:</p>
<img border="0" src="/blog/2009/09/seo-external-links-and-pagerank/image-0.png" style="display:block; margin:0px auto 10px; text-align:center; width: 400px; height: 121px;"/>
<p>I retrieved the PageRank from a generic PageRank tool. <a href="http://www.moz.org/">SEOmoz</a> was used to collect external link counts and external linking subdomains. Finally, Yahoo Site Explorer was used to retrieve external link counts to the domain in question. I chose to examine both external link counts from SEOmoz and Yahoo Site Explorer to get a better representation of data. SEOmoz compiles their data about once a month and does not have as many urls indexed as Yahoo, which explains why their numbers may be lagging behind the Yahoo Site Explorer external link counts.</p>
<p>Out of curiosity, I went on to plot the Page Rank data vs. Log (base 10) of the other data.</p>
<p>PageRank vs. Log of SEOmoz external link count:</p>
<img border="0" src="/blog/2009/09/seo-external-links-and-pagerank/image-1.png" style="display:block; margin:0px auto 10px; text-align:center; width: 400px; height: 275px;"/>
<p>PageRank vs. Log of SEOmoz external linking subdomain count:</p>
<img border="0" src="/blog/2009/09/seo-external-links-and-pagerank/image-2.png" style="display:block; margin:0px auto 10px; text-align:center; width: 400px; height: 272px;"/>
<p>PageRank vs. Log of Yahoo SiteExplorer external link count:</p>
<img border="0" src="/blog/2009/09/seo-external-links-and-pagerank/image-3.png" style="display:block; margin:0px auto 10px; text-align:center; width: 400px; height: 275px;"/>
<p>PageRank is described as a theoretical probability value on a logarithmic scale and it’s based on inbound links, PageRank of inbound links, and other factors such as Google visit data, search click-through rates, etc. The true popularity rank is a rank between 1 and X, where X is equal to the total number of webpages crawled by search engine A. After pages are individually ranked between 1 and X, they are scaled logarithmically between 0 and 10.</p>
<p>The takeaway from this data is when an “SEO report” gives advice to “get more external links”, it means:</p>
<ul>
<li>If your site has a PageRank of < 4, getting external links on the scale of hundreds may impact your existing PageRank or popularity</li>
<li>If your site has a PageRank of >= 4 and < 6, getting external links on the scale of thousands may impact your existing PageRank or popularity</li>
<li>If your site has a PageRank of >= 6 and < 8, getting external links on the scale of tens to hundreds of thousands may impact your existing PageRank or popularity</li>
<li>If your site has a PageRank of >= 8, you probably are already doing something right…</li>
</ul>
<p>Furthermore, even if a site improves external link counts, other factors will play into the PageRank algorithm. Additionally, keyword relevance and popularity play key roles in search engine results.</p>
Site Search on Railshttps://www.endpointdev.com/blog/2009/08/site-search-on-rails/2009-08-14T00:00:00+00:00Steph Skardal
<p>I was recently tasked with implementing site search using a commercially available site search application for one of our clients (<a href="https://www.gear.com/">Gear.com</a>). The basic implementation requires that a SOAP request be made and the XML data returned be parsed for display. The SOAP request contains basic search information, and additional information such as product pagination and sort by parameters. During the implementation in a Rails application, I applied a few unique solutions worthy of a blog article. :)</p>
<p>The first requirement I tackled was to design the web application in a way that produced search engine friendly canonical URLs. I used Rails routing to implement a basic search:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby">map.connect <span style="color:#d20;background-color:#fff0f0">':id'</span>, <span style="color:#a60;background-color:#fff0f0">:controller</span> => <span style="color:#d20;background-color:#fff0f0">'basic'</span>, <span style="color:#a60;background-color:#fff0f0">:action</span> => <span style="color:#d20;background-color:#fff0f0">'search'</span>
</code></pre></div><p>Any simple search path would be sent to the basic search query that performed the SOAP request followed by XML data parsing. For example,
<a href="https://www.gear.com/s/climb">https://www.gear.com/s/climb</a> is a search for “climb” and
<a href="https://www.gear.com/s/bike">https://www.gear.com/s/bike</a> for “bike”.</p>
<p>After the initial search, a user can refine the search by brand, merchant, category or price, or choose to sort the items, select a different page, or modify the number of items per page. I chose to force the order of refinement, for example, brand and merchant order were constrained with the following Rails routes:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby">map.connect <span style="color:#d20;background-color:#fff0f0">':id/brand/:rbrand'</span>, <span style="color:#a60;background-color:#fff0f0">:controller</span> => <span style="color:#d20;background-color:#fff0f0">'basic'</span>, <span style="color:#a60;background-color:#fff0f0">:action</span> => <span style="color:#d20;background-color:#fff0f0">'search'</span>
map.connect <span style="color:#d20;background-color:#fff0f0">':id/merch/:rmerch'</span>, <span style="color:#a60;background-color:#fff0f0">:controller</span> => <span style="color:#d20;background-color:#fff0f0">'basic'</span>, <span style="color:#a60;background-color:#fff0f0">:action</span> => <span style="color:#d20;background-color:#fff0f0">'search'</span>
map.connect <span style="color:#d20;background-color:#fff0f0">':id/brand/:rbrand/merch/:rmerch'</span>, <span style="color:#a60;background-color:#fff0f0">:controller</span> => <span style="color:#d20;background-color:#fff0f0">'basic'</span>, <span style="color:#a60;background-color:#fff0f0">:action</span> => <span style="color:#d20;background-color:#fff0f0">'search'</span>
</code></pre></div><p>Rather than allow different order of refinement parameters in the URLs, such as
<code>http://www.gear.com/s/climb/brand/Arcteryx/merch/Altrec</code> and <code>http://www.gear.com/s/climb/merch/Altrec/brand/Arcteryx</code>, the order of search refinement is always limited to the Rails routes specified above and the former URL would be allowed in this example.</p>
<p>For example, <code>http://www.gear.com/s/climb/brand/Arcteryx/merch/Altrec</code> is a valid URL for Arcteryx Altrec climb, <code>http://www.gear.com/s/climb/brand/Arcteryx</code> for Arcteryx climb, and <code>http://www.gear.com/s/climb/merch/Altrec</code> for Altrec climb.</p>
<p>All URLs on any given search result page are built with a single Ruby method to force the refinement and parameter order. The method input requires the existing refinement values, the new refinement key, and the new refinement value. The method builds a URL with all previously existing refinement values and adds the new refinement value. Rather than generating millions of URLs with the various refinement combinations of brand, merchant, category, price, items per page, pagination number, and sort method, this logic minimizes duplicate content. The use of Rails routes and the chosen URL structure also creates search engine friendly URLs that can be targeted for traffic. Below is example pseudocode with the URL-building method:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><span style="color:#080;font-weight:bold">def</span> <span style="color:#06b;font-weight:bold">build_url</span>(parameters, new_key, new_value)
<span style="color:#888"># set url to basic search information</span>
<span style="color:#888"># append brand info to url if parameters[:brand] exists or if new_key is brand</span>
<span style="color:#888"># append merchant info to url if parameters[:merchant] exists or if new_key is merchant</span>
<span style="color:#888"># append category info to url if parameters[:cat] exists or if new_key is cat</span>
<span style="color:#888"># ...</span>
<span style="color:#080;font-weight:bold">end</span>
</code></pre></div><p>The next requirement I encountered was breadcrumb functionality. Breadcrumbs are an important usability feature that provide the ability to navigate backwards in search and refinement history. Because of the canonical URL solution described above, the URL could not be used to indicate the search refinement history. For example, <code>http://www.gear.com/s/climb/brand/Arcteryx/merch/Altrec</code> does not indicate whether the user had refined by brand then merchant, or by merchant then brand. I investigated a few solutions having implemented similar breadcrumb functionality for other End Point clients, including appending the ‘#’ (hash or relative url) to the end of the URL with details of the user refinement path, using JavaScript to set a cookie containing the user refinement path whenever a link was clicked, and using a session variable to track the user refinement path. In the end, I found it easiest to use a single session variable to track the user refinement path. The session variable contained all information needed to display the breadcrumb with a bit of parsing.</p>
<p>For example, for the URL mentioned above, the session variable of ‘brand-Arcteryx:merch-Altrec’ would yield the breadcrumb:
<code>Your search: climb > Arcteryx > Altrec</code>
And the session variable ‘merch-Altrec:brand-Arcteryx’ would yield the breadcrumb:
<code>Your search: climb > Altrec > Arcteryx</code>. I could have used more than one session variable, but this solution worked out to be simple and comprised less than 10 lines of code.</p>
<p>Another interesting necessity was determining the best way to parse the XML data. I researched several XML parsers including XmlSimple, Hpricot, ReXML, and libxml. About a year ago, John Nunemaker reported on some benchmark testing of several of these packages (<a href="http://www.railstips.org/blog/archives/2008/08/11/parsing-xml-with-ruby/">Parsing XML with Ruby</a>). After some investigative work, I chose Hpricot because it was very easy to implement complex selectors that reminded me of jQuery selectors (which are also easy to use). The interesting thing that I noticed throughout the implementation was that the refinement parsing took much more time than the actual product parsing and formatting. For Gear.com, the number of products returned ranges from 20-60 and products were quickly parsed. The number of refinements returned ranged from very small for a distinct search <a href="https://www.gear.com/s/moccasym">Moccasym</a> (4 refinement options) to a general search <a href="https://www.gear.com/s/jacket">jacket</a> (50+ refinement options). If performance is an issue in the future, I can further investigate the use of libxml-ruby or other Ruby XML parsing tools that may improve the performance.</p>
<p>A final point of interest was the decision to tie the Rails application to the same database that drives the product pages (which was easily done). This decision was made to allow access of frontend taxonomy information for the product categorization. For example, if a user chooses to refine a specific by a category (<a href="https://www.gear.com/s/jacket?cat=kids-clothing">jacket in Kids Clothing</a>), the Rails app can retrieve all the taxonomy information for that category such as the display name, the number of products in that category, subcategories, and subsubcategories. This may be important information required for additional features, such as providing the ability to view the subcategories in this category or view other products in this category that aren’t shown in the search results.</p>
<p>I was happy to see the success of this project after working through the deliverables. Future work includes integration of additional search features common to many site search packages, such as implementing refinement by color and size, or retrieving recommended products or best sellers.</p>
nofollow in PageRank Sculptinghttps://www.endpointdev.com/blog/2009/06/nofollow-in-page-rank-sculpting/2009-06-24T00:00:00+00:00Steph Skardal
<p>Last week the SEO world reacted to <a href="https://www.mattcutts.com/blog/pagerank-sculpting/">Matt Cutts’ article</a> about the use of nofollow in PageRank sculpting.</p>
<p>Google uses the <a href="https://en.wikipedia.org/wiki/PageRank">PageRank</a> algorithm to calculate popularity of pages in the web. Popularity is only one factor in determining which pages are returned in search results (relevance to search terms is the other major factor). Other major search engines use similar popularity algorithms. Without describing the algorithm in detail, the important takeaways are:</p>
<ul>
<li>PageRank of a single page is influenced by all inbound (external links) links</li>
<li>PageRank of a single page is passed on to all outgoing links after being normalized and divided by the total number of outgoing links</li>
</ul>
<p>So, given page C with an inbound links from page A and B, where page A and B have equal page rank X, page A has 3 total external links and B has 5 total external links, page C receives more PageRank from page A than page B.</p>
<p><a href="https://1.bp.blogspot.com/_wWmWqyCEKEs/SkJx484BdLI/AAAAAAAABuY/HPa0OXDZC3c/s1600-h/pr1.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5350964530497287346" src="/blog/2009/06/nofollow-in-page-rank-sculpting/image-0.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 390px;"/></a></p>
<p>From an external link perspective, it’s great to get as many links as possible from a variety of sources that rank high and have a low number of external links. From an internal site perspective, it’s important to examine how PageRank is passed throughout a site to apply the best site architecture. In addition to designing a site architecture that pleases users and passes link juice throughout a site effectively, the rel=“nofollow” tag was adopted by several major search engines and was used as an additional tool to stop the flow of link juice from one page to another. The nofollow tag can also be used to identify paid links (early implementation) or to avoid passing links to external sites completely.</p>
<p>In the example above, rel=“nofollow” could be added to 2 links on page B which would result in the same PageRank passed from page B to page C as from page A to page C.</p>
<p><a href="https://1.bp.blogspot.com/_wWmWqyCEKEs/SkJx5C0oxcI/AAAAAAAABug/-WPznLcuCdI/s1600-h/pr2.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5350964532093699522" src="/blog/2009/06/nofollow-in-page-rank-sculpting/image-1.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 335px;"/></a></p>
<p>Then, at a recent SEO conference, Matt Cutts (head of the Google spam team) made a comment about how the PageRank algorithm changed its use of nofollow and just last week, it was announced that the PageRank algorithm would no longer use the nofollow attribute in PageRank sculpting. Any link with the nofollow attribute will no longer reduce the count of outgoing page links to improve link juice passed on to other pages, but link juice will still not be passed from one link to another with the nofollow attribute.</p>
<p>In the ongoing example, the link juice passed from page B to page C will be less than from page A to C because it has more outgoing links, even if they are nofollow links.</p>
<p><a href="https://4.bp.blogspot.com/_wWmWqyCEKEs/SkJx5Z86AuI/AAAAAAAABuo/e-bVOvB3Eik/s1600-h/pr3.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5350964538302399202" src="/blog/2009/06/nofollow-in-page-rank-sculpting/image-2.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 335px;"/></a></p>
<p>One <a href="https://moz.com/blog/google-says-yes-you-can-still-sculpt-pagerank-no-you-cant-do-it-with-nofollow">SEOmoz article</a> I read suggests that SEO best practices will now be to recommend blog owners to disallow comments that may contain external links to prevent the dilution of link juice. Other potential solutions would be to filter out links from user generated content (comments or qna specifically), use iframes to display any user generated content, or embed flash or java with external links. The nofollow attribute may be used to stop the flow of link juice to external pages, however, it may no longer be used for internal PageRank sculpting.</p>
SEO Ecommercehttps://www.endpointdev.com/blog/2009/04/ecommerce-and-seo/2009-04-20T00:00:00+00:00Steph Skardal
<p>I recently read an article that discusses <a href="https://web.archive.org/web/20090311033516/http://yoast.com/magento-seo/">Magento SEO</a> problems and solutions. This got me to think about common search engine optimization issues that I’ve seen in e-commerce. Below are some highlighted e-commerce search engine optimization issues. The <a href="https://github.com/spree/demo">Spree Demo</a>, <a href="http://demo.icdevgroup.org/i/demo1">Interchange Demo</a>, and <a href="https://web.archive.org/web/20090716030438/http://demo.magentocommerce.com/">Magento Demo</a> are used as references.</p>
<h3 id="duplicate-home-pages-www-non-www-indexhtml">Duplicate Home Pages (www, non-www, index.html)</h3>
<p>Duplicate home pages can come in the form of a homepage with www and without www, a homepage in the form of http://www.domain.com/ and a homepage with some variation of “index” appended to the url, or a combination of the two. In the Interchange demo, <a href="http://demo.icdevgroup.org/i/demo1">http://demo.icdevgroup.org/i/demo1</a> and <a href="http://demo.icdevgroup.org/i/demo1/index.html">http://demo.icdevgroup.org/i/demo1/index.html</a> are duplicate, http://demo.spreecommerce.com/ and http://demo.spreecommerce.com/products/ in the Spree demo, and finally http://demo.magentocommerce.com/ and http://demo.magentocommerce.com/index.php in the Magento demo.</p>
<p>External links positively influence search engine performance more if they are pointing to one index page rather than being divided between two or three home pages. Since the homepage most likely receives the most external links, this issue can be more problematic than other generated duplicate content. I’ve also seen this happen in several content management systems.</p>
<p><a href="https://web.archive.org/web/20090311033516/http://yoast.com/magento-seo/">This article</a> provides directions on mod_rewrite use to apply a 301 redirect from the www.domain.com/index.php homepage to www.domain.com. This solution or other redirect solutions can be applied to Spree, Interchange, and other ecommerce platforms.</p>
<h3 id="irrelevant-product-urls">Irrelevant Product URLs</h3>
<p>A search engine optimization best practice is to provide relevant and indicative text in the product urls. In the Interchange demo, the default catalog uses the product sku in the product url (http://demo.icdevgroup.org/i/demo1/os28073.html). In Magento and Spree, product permalinks with relevant text are used in the product url. In wordpress, the author has the ability to set permalinks for articles. I am unsure if Magento gives you the ability to customize product urls. Spree does not currently give you the ability to manage custom product permalinks. However, for all of these ecommerce platforms, these fixes may all be in the works since it is important for ecommerce platforms to implement search engine optimization best practices.</p>
<h3 id="duplicate-product-content">Duplicate product content</h3>
<p>I’ve observed several situations where products divided into multiple taxonomies results in duplicate content creation via different user navigation paths. For example, in the Spree demo, the “Ruby Baseball Jersey” can be reached through the Ruby brand page, the Clothing page, or the homepage. The three generated duplicate content urls are http://demo.spreecommerce.com/products/ruby-on-rails-ringer-t-shirt, http://demo.spreecommerce.com/t/brands/ruby/p/ruby-baseball-jersey, and http://demo.spreecommerce.com/t/categories/clothing/shirts/p/ruby-baseball-jersey.</p>
<p>Another example of this can be found in the Interchange demo. The left navigation taxonomy tree provides links to any product url with “?open=X,Y,Z” appended to the url. The “open” query string indicates how the DHTML tree should be displayed. For example, the “Digger Hand Trencher” has a base url of http://demo.icdevgroup.org/i/demo1/os28076.html. Depending on which tree nodes are exploded, the product can be reached at http://demo.icdevgroup.org/i/demo1/os28076.html?open=0,11,13,19, http://demo.icdevgroup.org/i/demo1/os28076.html?open=0,11,13, etc. This standard demo functionality yields a lot of duplicate content.</p>
<p>In Magento, products are the in the form of www.domain.com/product-name, although the article I mentioned above mentions that www.domain.com/category/product.html product urls were generated. Perhaps this was a recent fix, or perhaps the demo is configured to avoid generating this type of duplicate content.</p>
<p>Duplicate product page content is often used to indicate which breadcrumb should display or to track user click-through behavior (for example, did a user click on a “featured product”? a “best seller”? a specific “product advertisement”?). In Interchange, session ids are appended to urls which is another source of duplicate content. Instead of using the url to track user navigation or behavior, several other solutions such as using cookies, using a ‘#’ (hash), or using session data can be used to avoid duplicate content generation.</p>
<h3 id="performance">Performance</h3>
<p>Performance should not be overlooked in ecommerce for search engine optimization. In March of 2008, Google wrote about <a href="https://adwords.googleblog.com/2008/03/landing-page-load-time-will-soon-be.html">how landing page load time will be incorporated into the Quality Score for Google Adwords</a>—which is also believed to apply to regular search results. And github recently released some data on <a href="https://github.com/blog/368-keeping-googlebot-happy">how performance improvements influenced http://www.github.com/ Googlebot visits</a>.</p>
<p>Keeping a high content to text ratio, consolidation, minification, and gzipping CSS and JavaScript, and minimizing the use of JavaScript-based suckerfish can all improve search engine performance.</p>
<p>The Interchange default catalog has a simple template with minimal css and javascript includes, so the developer is responsible for sticking to best performance practices. The Magento demo appears to have decent content to text ratio, but still requires 5 css files that should be consolidated and minified if they are included on every page. Finally, Spree has undergone some changes in the last month and is moving in the direction of including one consolidated javascript file plus any javascript required for extensions on every page, and the <a href="https://groups.google.com/forum/#!topic/spree-user/gB3hZ3sgLpw">upcoming release of Spree 0.8.0</a> will have considerable frontend view improvements.</p>
<p>Ecommerce platforms should have decent performance—<a href="http://yslow.org/">yslow</a> or <a href="http://shop.oreilly.com/product/9780596529307.do">this book on high performance website essentials</a> are good resources.</p>
<h3 id="lacking-basic-cms-management">Lacking basic CMS management</h3>
<p>Basic CMS management such as the ability to manage and update page titles and page meta data is something that has been overlooked by ecommerce platforms in the past, but appears to have been given more attention recently. An ecommerce solution should also have functionality to create and manage static pages.</p>
<p>The Interchange demo does not have meta description and keyword functionality, however, page titles are equal to product names which is an acceptable default. It’s also very simple to add a static content page (as a developer) and would require just a bit more effort to have this content managed by a database in Interchange. The Spree core is missing some basic CMS management such as page title and meta data management, but this functionality is currently in development. One Spree contributer developed <a href="https://web.archive.org/web/20090619060215/http://github.com/PeterBerkenbosch/spree-static-content/tree/master">a Spree extension that provides management of simple static pages using a WYSIWYG editor</a>. At the moment, Magento appears to have the most traditional content management system functionality out of the box.</p>
<p>Another area to improve CMS within Ecommerce is to determine a solution to integrate a blog. A quick Google search of “magento add blog” revealed <a href="https://web.archive.org/web/20090508093030/http://chasesagum.com/setup-a-blog-inside-your-magento-store">how to set up a wordpress blog in Magento with an extension</a>. One of End Point’s clients, <a href="https://www.ccibeauty.com/">CCI Beauty</a>, also has <a href="https://www.ccibeauty.com/blog/">wordpress integrated into their Interchange setup</a>. Finally, there has been discussion about the development of “Spradiant”, or <a href="https://groups.google.com/forum/#!searchin/spree-user/radiant%7Csort:date">mixing spree and radiant</a>.</p>
<p>Another missed opportunity in ecommerce platforms is finding a solution to elegantly blend content and product listings to target specific keywords. A “landing page” can have a page title, meta data, and content targeted towards a specific terms. http://www.backcountry.com/store/gear/arcteryx-vests.html and http://www.backcountry.com/store/gear/cargo-pant.html are examples of targeted terms with corresponding products. Going one step farther, search pages themselves can have managed content to attract keywords, such as a page title, and meta data for specific high traffic keywords with the related products. For example, http://www.domain.com/s/ruby_shirt could be a search page for “Ruby Shirt” which contains meaningful content and relevant products.</p>
<h3 id="mishandled-product-pagination">Mishandled Product Pagination</h3>
<p>Finding a search engine optimization solution for pagination can be a difficult problem in ecommerce. When there are less than 100 products for a site, this shouldn’t be an issue because a simple taxonomy can appropriately group the products with low crawl depth. A website with 10,000 products must balance between keeping a low taxonomy depth to minimize crawl depth and ensure that all products are listed and indexable.</p>
<p>For example, products may be divided and fit into three levels of navigation: category, subcategory, and group. If there are 10,000 products, divided into 10 categories, 10 subcategories per category, and 10 product groups per category, 10 products can be shown on each group per page with no pagination. However, product taxonomy is not always so ideal. In some groups there may be 2 products and in others there may be 30. Pagination, or pages with an offset of product listings are generated to accommodate these product listings (for example, http://www.backcountry.com/store/group/61/Sun-Hats-Rain-Hats-Safari-Hats.html, http://www.backcountry.com/store/group/61/Sun-Hats-Rain-Hats-Safari-Hats-p1.html).</p>
<p>A few problems can arise from the pagination solution. First, by web 2.0 standards, the content should be generated via ajax. An SEO friendly ajax solution must be implemented—where the onclick event refreshes the content, but the links are still crawlable via search engine bots. Second, page 1 with no product offset will have 1 level less of crawl depth, therefore it will receive the most link juice from it’s parent page (subcategory). As a result, there must be thoughtful analysis of which products to present on that page: should high traffic pages get the traffic? should popular items be listed on the first page? should low traffic products be listed to try to bump the traffic on those pages? should products with the most “user interaction” (reviews, qna, ratings) be shown on that page? Another problem that comes up is that the page meta data and title will most likely be very similar since the content is a list of similar products. These two pages can essentially be competing for traffic and may be counted as duplicate content if the page titles and meta data are equal.</p>
<p>Interchange uses the <a href="http://docs.icdevgroup.org/cgi-bin/online/tags/more_list.html">more list</a> to handle pagination, but this functionality is not search engine friendly as it generates urls such as http://demo.icdevgroup.org/i/demo1/scan/MM=3ffffa066192cba677e1428d7461ddc9:10:19:10.html?mv_more_ip=1&mv_nextpage=results&mv_arg=, http://demo.icdevgroup.org/i/demo1/scan/MM=3ffffa066192cba677e1428d7461ddc9:20:27:10.html?mv_more_ip=1&mv_nextpage=results&mv_arg=, etc. The Spree demo had some pagination implementation, but upon recent frontend changes, it is no longer included in the demo. The Magento demo was carefully arranged so that product group pages have no more than 9 products to avoid showing any pagination functionality. However, when modifying the number of products displayed per group or using the “Sort By” mechanism, ?limit=Y and &order=X&dir=asc is appended to the url—which can produce a large volume of duplicate content (try filters on <a href="https://web.archive.org/web/20090227041133/http://demo.magentocommerce.com/apparel/shirts">this page</a>).</p>
<p>It is difficult to determine which of the above problems is the most problematic. From personal experience, I have been involved in tackling all duplicate content issues, and then moving on to “optimization” opportunities such as enhancing the content management system. At the very least, developers and users of any ecommerce platform should be aware of common search engine optimization issues.</p>