feat: optimize head tags (#40630)
Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
625469c82f
commit
3ec468be38
@ -4,10 +4,9 @@ import { Provider } from 'react-redux';
|
|||||||
import { I18nextProvider } from 'react-i18next';
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
|
||||||
import i18n from './i18n/config';
|
import i18n from './i18n/config';
|
||||||
import headComponents from './src/head';
|
|
||||||
import { createStore } from './src/redux/createStore';
|
import { createStore } from './src/redux/createStore';
|
||||||
|
|
||||||
import layoutSelector from './utils/gatsby/layoutSelector';
|
import layoutSelector from './utils/gatsby/layoutSelector';
|
||||||
|
import { getheadTagComponents, getPostBodyComponents } from './utils/tags';
|
||||||
|
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
|
|
||||||
@ -23,26 +22,6 @@ wrapRootElement.propTypes = {
|
|||||||
element: PropTypes.any
|
element: PropTypes.any
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: put these in a common utils file.
|
|
||||||
const mathJaxCdn = {
|
|
||||||
address:
|
|
||||||
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/' +
|
|
||||||
'2.7.4/MathJax.js?config=TeX-AMS_HTML',
|
|
||||||
id: 'mathjax',
|
|
||||||
key: 'mathjax',
|
|
||||||
type: 'text/javascript'
|
|
||||||
};
|
|
||||||
|
|
||||||
const stripeScript = {
|
|
||||||
address: 'https://js.stripe.com/v3/',
|
|
||||||
id: 'stripe-js',
|
|
||||||
key: 'stripe-js',
|
|
||||||
type: 'text/javascript'
|
|
||||||
};
|
|
||||||
|
|
||||||
const challengeRE = new RegExp('/learn/[^/]+/[^/]+/[^/]+/?$');
|
|
||||||
const donateRE = new RegExp('/donate/?$');
|
|
||||||
|
|
||||||
export const wrapPageElement = layoutSelector;
|
export const wrapPageElement = layoutSelector;
|
||||||
|
|
||||||
export const onRenderBody = ({
|
export const onRenderBody = ({
|
||||||
@ -50,52 +29,6 @@ export const onRenderBody = ({
|
|||||||
setHeadComponents,
|
setHeadComponents,
|
||||||
setPostBodyComponents
|
setPostBodyComponents
|
||||||
}) => {
|
}) => {
|
||||||
setHeadComponents([...headComponents]);
|
setHeadComponents(getheadTagComponents());
|
||||||
const scripts = [
|
setPostBodyComponents(getPostBodyComponents(pathname));
|
||||||
<script
|
|
||||||
async={true}
|
|
||||||
key='gtag-script'
|
|
||||||
src='https://www.googletagmanager.com/gtag/js?id=AW-795617839'
|
|
||||||
/>,
|
|
||||||
<script
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: `
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
gtag('config', 'AW-795617839');
|
|
||||||
`
|
|
||||||
}}
|
|
||||||
key='gtag-dataLayer'
|
|
||||||
/>
|
|
||||||
];
|
|
||||||
|
|
||||||
if (
|
|
||||||
pathname.includes('/learn/coding-interview-prep/rosetta-code') ||
|
|
||||||
pathname.includes('/learn/coding-interview-prep/project-euler')
|
|
||||||
) {
|
|
||||||
scripts.push(
|
|
||||||
<script
|
|
||||||
async={false}
|
|
||||||
id={mathJaxCdn.id}
|
|
||||||
key={mathJaxCdn.key}
|
|
||||||
src={mathJaxCdn.address}
|
|
||||||
type={mathJaxCdn.type}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (challengeRE.test(pathname) || donateRE.test(pathname)) {
|
|
||||||
scripts.push(
|
|
||||||
<script
|
|
||||||
async={true}
|
|
||||||
id={stripeScript.id}
|
|
||||||
key={stripeScript.key}
|
|
||||||
src={stripeScript.address}
|
|
||||||
type={stripeScript.type}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPostBodyComponents(scripts.filter(Boolean));
|
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"title": "免费学习编程 – 适合忙碌人士的编程课程",
|
"title": "免费学习编程 – 适合忙碌人士的编程课程",
|
||||||
"description": "在家学习编程,构建项目,获得认证。自 2014 年以来,已有超过 40,000 名 freeCodeCamp 学员入职谷歌、苹果、亚马逊、微软等科技公司。",
|
"description": "在家学习编程,构建项目,获得认证。自 2014 年以来,已有超过 40,000 名 freeCodeCamp 学员入职谷歌、苹果、亚马逊、微软等科技公司。",
|
||||||
|
"social-description": "Learn to Code — For Free",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"javascript",
|
"javascript",
|
||||||
"js",
|
"js",
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"title": "Learn to Code — For Free — Coding Courses for Busy People",
|
"title": "Learn to Code — For Free — Coding Courses for Busy People",
|
||||||
"description": "Learn to code — for free.",
|
"description": "Learn to Code — For Free",
|
||||||
|
"social-description": "Learn to Code — For Free",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"javascript",
|
"javascript",
|
||||||
"js",
|
"js",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"title": "Aprende a codificar gratis: cursos de programación para personas ocupadas",
|
"title": "Aprende a codificar gratis: cursos de programación para personas ocupadas",
|
||||||
"description": "Aprende a codificar en casa. Construye proyectos. Obtén certificaciones. Desde el 2014, más de 40,000 graduados de freeCodeCamp.org han conseguido trabajos en empresas de tecnología como Google, Apple, Amazon y Microsoft.",
|
"description": "Aprende a codificar en casa. Construye proyectos. Obtén certificaciones. Desde el 2014, más de 40,000 graduados de freeCodeCamp.org han conseguido trabajos en empresas de tecnología como Google, Apple, Amazon y Microsoft.",
|
||||||
|
"social-description": "Learn to Code — For Free",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"javascript",
|
"javascript",
|
||||||
"js",
|
"js",
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
const translationsSchema = {
|
const translationsSchema = {
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Learn to Code for Free – Coding Courses for Busy People',
|
title: 'Learn to Code — For Free — Coding Courses for Busy People',
|
||||||
description:
|
description: 'Learn to Code — For Free',
|
||||||
'Learn to code at home. Build projects. Earn certifications. Since 2014, more than 40,000 freeCodeCamp.org graduates have gotten jobs at tech companies including Google, Apple, Amazon, and Microsoft.',
|
'social-description': 'Learn to Code — For Free',
|
||||||
keywords: [
|
keywords: [
|
||||||
'javascript',
|
'javascript',
|
||||||
'js',
|
'js',
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import meta from './meta';
|
|
||||||
|
|
||||||
const metaAndStyleSheets = meta.map((element, i) => ({
|
|
||||||
...element,
|
|
||||||
key: `meta-stylesheet-${i}`,
|
|
||||||
props: { ...element.props, key: `meta-stylesheet-${i}` }
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default metaAndStyleSheets;
|
|
@ -1,34 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
const meta = [
|
|
||||||
<meta content='freeCodeCamp.org' name='og:title' />,
|
|
||||||
<meta
|
|
||||||
content={
|
|
||||||
'Learn to code. Build projects. Earn certifications.' +
|
|
||||||
'Since 2015, 40,000 graduates have gotten jobs at tech ' +
|
|
||||||
'companies including Google, Apple, Amazon, and Microsoft.'
|
|
||||||
}
|
|
||||||
name='og:description'
|
|
||||||
/>,
|
|
||||||
<meta
|
|
||||||
content='https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920X1080-indigo.png'
|
|
||||||
property='og:image'
|
|
||||||
/>,
|
|
||||||
<meta content='summary_large_image' key='twitter:card' name='twitter:card' />,
|
|
||||||
<meta
|
|
||||||
content='https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920X1080-indigo.png'
|
|
||||||
name='twitter:image:src'
|
|
||||||
/>,
|
|
||||||
<meta content='freeCodeCamp.org' name='twitter:title' />,
|
|
||||||
<meta
|
|
||||||
content={
|
|
||||||
'Learn to code. Build projects. Earn certifications.' +
|
|
||||||
'Since 2015, 40,000 graduates have gotten jobs at tech ' +
|
|
||||||
'companies including Google, Apple, Amazon, and Microsoft.'
|
|
||||||
}
|
|
||||||
name='twitter:description'
|
|
||||||
/>,
|
|
||||||
<meta content='$ilp.uphold.com/LJmbPn7WD4JB' name='monetization' />
|
|
||||||
];
|
|
||||||
|
|
||||||
export default meta;
|
|
@ -1,11 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styleSheets from './styleSheets';
|
|
||||||
|
|
||||||
const preloads = styleSheets.map(styleSheet => (
|
|
||||||
<React.Fragment key={`preload-${styleSheet.props.href}`}>
|
|
||||||
<link as='style' href={styleSheet.props.href} rel='preload' />
|
|
||||||
{styleSheet}
|
|
||||||
</React.Fragment>
|
|
||||||
));
|
|
||||||
|
|
||||||
export default preloads;
|
|
@ -1,3 +0,0 @@
|
|||||||
const styleSheets = [];
|
|
||||||
|
|
||||||
export default styleSheets;
|
|
@ -12,8 +12,6 @@ export default class HTML extends React.Component {
|
|||||||
content='width=device-width, initial-scale=1.0, shrink-to-fit=no'
|
content='width=device-width, initial-scale=1.0, shrink-to-fit=no'
|
||||||
name='viewport'
|
name='viewport'
|
||||||
/>
|
/>
|
||||||
<link as='style' href='/css/bootstrap.min.css' rel='preload' />
|
|
||||||
<link href='/css/bootstrap.min.css' rel='stylesheet' />
|
|
||||||
{this.props.headComponents}
|
{this.props.headComponents}
|
||||||
</head>
|
</head>
|
||||||
<body {...this.props.bodyAttributes}>
|
<body {...this.props.bodyAttributes}>
|
||||||
|
85
client/utils/tags.js
Normal file
85
client/utils/tags.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withPrefix } from 'gatsby';
|
||||||
|
import i18next from 'i18next';
|
||||||
|
|
||||||
|
export const getheadTagComponents = () => {
|
||||||
|
const socialImage =
|
||||||
|
'https://cdn.freecodecamp.org/platform/universal/fcc_meta_1920X1080-indigo.png';
|
||||||
|
const pathToBootstrap = withPrefix('/css/bootstrap.min.css');
|
||||||
|
return [
|
||||||
|
<link
|
||||||
|
as='style'
|
||||||
|
href={pathToBootstrap}
|
||||||
|
key='boostrap-min-preload'
|
||||||
|
rel='preload'
|
||||||
|
/>,
|
||||||
|
<link href={pathToBootstrap} key='boostrap-min' rel='stylesheet' />,
|
||||||
|
<meta content='freeCodeCamp.org' key='og:title' name='og:title' />,
|
||||||
|
<meta
|
||||||
|
content={i18next.t('meta.social-description')}
|
||||||
|
key='og:description'
|
||||||
|
name='og:description'
|
||||||
|
/>,
|
||||||
|
<meta content={socialImage} key='og:image' property='og:image' />,
|
||||||
|
<meta
|
||||||
|
content='summary_large_image'
|
||||||
|
key='twitter:card'
|
||||||
|
name='twitter:card'
|
||||||
|
/>,
|
||||||
|
<meta
|
||||||
|
content={socialImage}
|
||||||
|
key='twitter:image:src'
|
||||||
|
name='twitter:image:src'
|
||||||
|
/>,
|
||||||
|
<meta
|
||||||
|
content='freeCodeCamp.org'
|
||||||
|
key='twitter:title'
|
||||||
|
name='twitter:title'
|
||||||
|
/>,
|
||||||
|
<meta
|
||||||
|
content={i18next.t('meta.social-description')}
|
||||||
|
key='twitter:description'
|
||||||
|
name='twitter:description'
|
||||||
|
/>,
|
||||||
|
<meta
|
||||||
|
content='$ilp.uphold.com/LJmbPn7WD4JB'
|
||||||
|
key='monetization'
|
||||||
|
name='monetization'
|
||||||
|
/>
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPostBodyComponents = pathname => {
|
||||||
|
let scripts = [];
|
||||||
|
const challengesPathRE = new RegExp('/learn/[^/]+/[^/]+/[^/]+/?$');
|
||||||
|
const donatePathRE = new RegExp('/donate/?$');
|
||||||
|
const mathJaxScriptElement = (
|
||||||
|
<script
|
||||||
|
async={false}
|
||||||
|
id='mathjax'
|
||||||
|
key='mathjax'
|
||||||
|
src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML'
|
||||||
|
type='text/javascript'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const stripeScriptElement = (
|
||||||
|
<script
|
||||||
|
async={true}
|
||||||
|
id='stripe-js'
|
||||||
|
key='stripe-js'
|
||||||
|
src='https://js.stripe.com/v3/'
|
||||||
|
type='text/javascript'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
pathname.includes('/learn/coding-interview-prep/rosetta-code') ||
|
||||||
|
pathname.includes('/learn/coding-interview-prep/project-euler')
|
||||||
|
) {
|
||||||
|
scripts.push(mathJaxScriptElement);
|
||||||
|
}
|
||||||
|
if (challengesPathRE.test(pathname) || donatePathRE.test(pathname)) {
|
||||||
|
scripts.push(stripeScriptElement);
|
||||||
|
}
|
||||||
|
return scripts.filter(Boolean);
|
||||||
|
};
|
115
cypress/integration/tags.js
Normal file
115
cypress/integration/tags.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/* global cy */
|
||||||
|
describe('The Document Metadata', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.visit('/');
|
||||||
|
cy.document();
|
||||||
|
});
|
||||||
|
|
||||||
|
const social = {
|
||||||
|
description: 'Learn to Code — For Free'
|
||||||
|
};
|
||||||
|
|
||||||
|
const challengs = {
|
||||||
|
responsiveWebDesign:
|
||||||
|
'/learn/responsive-web-design/basic-html-and-html5/say-hello-to-html-elements',
|
||||||
|
rosetaCode: '/learn/coding-interview-prep/rosetta-code/100-doors',
|
||||||
|
projectEuler:
|
||||||
|
'/learn/coding-interview-prep/project-euler/problem-1-multiples-of-3-and-5'
|
||||||
|
};
|
||||||
|
|
||||||
|
const scripts = {
|
||||||
|
stripe: {
|
||||||
|
selector: 'body script[id="stripe-js"]',
|
||||||
|
src: 'https://js.stripe.com/v3/'
|
||||||
|
},
|
||||||
|
mathjax: {
|
||||||
|
selector: 'body script[id="mathjax"]',
|
||||||
|
src:
|
||||||
|
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS_HTML'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
it('landing page has correct <meta> for description', () => {
|
||||||
|
cy.get('head meta[name="description"]').should(
|
||||||
|
'have.attr',
|
||||||
|
'content',
|
||||||
|
'Learn to Code — For Free'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('landing page has correct <meta> for og title', () => {
|
||||||
|
cy.get('head meta[name="og:title"]').should(
|
||||||
|
'have.attr',
|
||||||
|
'content',
|
||||||
|
'freeCodeCamp.org'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('landing page has correct <meta> for og description', () => {
|
||||||
|
cy.get('head meta[name="og:description"]').should(
|
||||||
|
'have.attr',
|
||||||
|
'content',
|
||||||
|
social.description
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('landing page has correct <meta> for twitter title', () => {
|
||||||
|
cy.get('head meta[name="twitter:title"]').should(
|
||||||
|
'have.attr',
|
||||||
|
'content',
|
||||||
|
'freeCodeCamp.org'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('landing page has correct <meta>for twitter description', () => {
|
||||||
|
cy.get('head meta[name="twitter:description"]').should(
|
||||||
|
'have.attr',
|
||||||
|
'content',
|
||||||
|
social.description
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('landing page should not have stripe body script', () => {
|
||||||
|
cy.reload();
|
||||||
|
cy.get(scripts.stripe.selector).should('not.exist');
|
||||||
|
});
|
||||||
|
it('landing page should not have mathjax body script', () => {
|
||||||
|
cy.reload();
|
||||||
|
cy.get(scripts.mathjax.selector).should('not.exist');
|
||||||
|
});
|
||||||
|
it('responsive webdesign challenges should not have mathjax body script', () => {
|
||||||
|
cy.visit(challengs.responsiveWebDesign);
|
||||||
|
cy.reload();
|
||||||
|
cy.get(scripts.mathjax.selector).should('not.exist');
|
||||||
|
});
|
||||||
|
it('donate page should have stripe body script', () => {
|
||||||
|
cy.visit('/donate');
|
||||||
|
cy.reload();
|
||||||
|
cy.get(scripts.stripe.selector).should(
|
||||||
|
'have.attr',
|
||||||
|
'src',
|
||||||
|
scripts.stripe.src
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('responsive webdesign challenges should have stripe body script', () => {
|
||||||
|
cy.visit(challengs.responsiveWebDesign);
|
||||||
|
cy.reload();
|
||||||
|
cy.get(scripts.stripe.selector).should(
|
||||||
|
'have.attr',
|
||||||
|
'src',
|
||||||
|
scripts.stripe.src
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('project euler challenges should have mathjax body script', () => {
|
||||||
|
cy.visit(challengs.projectEuler);
|
||||||
|
cy.reload();
|
||||||
|
cy.get(scripts.mathjax.selector).should(
|
||||||
|
'have.attr',
|
||||||
|
'src',
|
||||||
|
scripts.mathjax.src
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('rosetta code challenges should have mathjax body script', () => {
|
||||||
|
cy.visit(challengs.projectEuler);
|
||||||
|
cy.reload();
|
||||||
|
cy.get(scripts.mathjax.selector).should(
|
||||||
|
'have.attr',
|
||||||
|
'src',
|
||||||
|
scripts.mathjax.src
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user