Giter Site home page Giter Site logo

based-ghost / react-seo-friendly-spa-template Goto Github PK

View Code? Open in Web Editor NEW
48.0 2.0 20.0 29.36 MB

React PWA/SPA template initially scaffolded with CRA (Create React App) and configured for SEO. Makes use of prerendering and other techniques/packages in order to achieve a perfect "Lighthouse Score".

License: MIT License

HTML 3.44% TypeScript 78.17% JavaScript 1.44% SCSS 16.95%
react pwa spa typescript prerender react-helmet react-ga react-snapshot seo google-analytics

react-seo-friendly-spa-template's Introduction

react-seo-friendly-spa-template

React PWA/SPA template configured for SEO (initially scaffolded with Create React App).

Features:

  • TypeScript
  • Incorporates styled-components
  • Route transitions handled using react-transition-group
  • Built entirely with React Hooks API (no legacy class components)
  • Google analytics management with react-ga
  • Route meta tag management with react-helmet-async
  • Configured to serve prerendered static HTML with react-snap
  • Custom BackToTop.tsx component
  • Custom ToggleTheme.tsx component that handles light/dark theme transitions

Demo

demo

General Overview

This is the React version based on my Vue SEO template which you can find here: vue-seo-friendly-spa-template

Technology Stack Overview

Create React App

initial scaffolding

react-helmet-async

react-helmet-async - plugin that allows you to manage your app's meta information. It is a reusable React component that will manage all of your changes to the document head - Helmet takes plain HTML tags and outputs plain HTML tags. It's dead simple, and React beginner friendly. This is the thread safe fork of react-helmet.

I have it configured to use one more level of abstraction, where I have the Helmet component and child meta tags broken out to its own component MetaInfo.tsx - referenced at the root of the app i App.tsx to initialize data and then referenced in each route component to override route-specific values (Home.tsx, About.tsx, NotFound404.tsx):

MetaInfo.tsx

import { Helmet } from 'react-helmet-async';
import type { FunctionComponent } from 'react';
import { getRouteMetaInfo, type MetaInfoProps } from '../config/routes.config';
import { APP_NAME, BASE_URL, AUTHOR_NAME, DEFAULT_LANG, DEFAULT_LOCALE } from '../config/env.config';

const {
  title: DEFAULT_TITLE,
  description: DEFAULT_DESCRIPTION
} = getRouteMetaInfo('Home');

const MetaInfo: FunctionComponent<MetaInfoProps> = ({
  meta = [],
  defer = false,
  lang = DEFAULT_LANG,
  title = DEFAULT_TITLE,
  locale = DEFAULT_LOCALE,
  description = DEFAULT_DESCRIPTION
}) => {
  const url = window?.location.href || 'unknown';

  return (
    <Helmet
      defer={defer}
      title={title}
      htmlAttributes={{ lang }}
      titleTemplate={`${APP_NAME} | %s`}
      link={[
        {
          rel: 'canonical',
          href: url
        }
      ]}
      meta={[
        {
          name: 'description',
          content: description
        },
        {
          property: 'og:description',
          content: description
        },
        {
          property: 'og:title',
          content: title
        },
        {
          property: 'og:site_name',
          content: APP_NAME
        },
        {
          property: 'og:type',
          content: 'website'
        },
        {
          property: 'og:url',
          content: url
        },
        {
          property: 'og:locale',
          content: locale
        },
        {
          property: 'og:image',
          content: `${BASE_URL}logo192.png`
        },
        {
          name: 'author',
          content: AUTHOR_NAME
        }
      ].concat(meta)}
    />
  );
};

export default MetaInfo;

...and used in About component

import type { FunctionComponent } from 'react';
import { Alert, MetaInfo } from '../../components';
import { getRouteMetaInfo } from '../../config/routes.config';

const About: FunctionComponent = () => (
  <div className="container view-wrapper">
    <MetaInfo {...getRouteMetaInfo('About')} />
    <Alert
      title="About Page"
      alertAnimation="rubberBand_animation 1s"
      subTitle="Very interesting information may go here."
    />
  </div>
);

export default About;

react-ga

react-ga - This is a JavaScript module that can be used to include Google Analytics tracking code in a website or app that uses React for its front-end codebase. It does not currently use any React code internally, but has been written for use with a number of Mozilla Foundation websites that are using React, as a way to standardize our GA Instrumentation across projects.

My preferred configuration - in a custom hook that initializes your google analytics settings and contains an effect that reacts to the location object that is retrieved from the referenced react-router-dom hook useLocation - usePageTracker.ts:

import ReactGA from 'react-ga';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { isLocationValidRoute } from '../config/routes.config';

// Initialize the react-ga plugin using your issued GA tracker code + options
ReactGA.initialize('UA-000000-01', {
  testMode: process.env.NODE_ENV === 'test',
  debug: process.env.NODE_ENV !== 'production',
  gaOptions: {
    cookieFlags: 'max-age=7200;secure;samesite=none'
  }
});

// Define custom hook to handle page tracking
const usePageTracker = (): void => {
  const location = useLocation();

  useEffect(() => {
    const { pathname, search } = location;

    if (isLocationValidRoute(pathname)) {
      const page = pathname + search;
      ReactGA.set({ page });
      ReactGA.pageview(page);
    }
  }, [location]);
};

export default usePageTracker;

...and then use that hook in the root of the application tree:

e.g. in the App.tsx component

import Layout from './Layout';
import type { FunctionComponent } from 'react';
import { routes } from './config/routes.config';
import { MetaInfo, NotFound404 } from './components';
import { usePageTracker, useScrollToTop } from './hooks';
import { useLocation, Route, Routes } from 'react-router-dom';
import { CSSTransition, SwitchTransition } from 'react-transition-group';

const App: FunctionComponent = () => {
  useScrollToTop();
  usePageTracker();
  const location = useLocation();

  return (
    <Layout>
      <MetaInfo />
      <SwitchTransition mode="out-in">
        <CSSTransition
          timeout={250}
          classNames="fade"
          key={location.key}
        >
          <Routes location={location}>
            {routes.map(({ path, Component }) => (
              <Route
                key={path}
                path={path}
                element={<Component />}
              />
            ))}
            <Route
              path="*"
              element={<NotFound404 />}
            />
          </Routes>
        </CSSTransition>
      </SwitchTransition>
    </Layout>
  );
};

export default App;

react-snap

react-snapshot - Pre-renders a web app into static HTML. Uses Headless Chrome to crawl all available links starting from the root. Heavily inspired by prep and react-snapshot, but written from scratch. Uses best practices to get the best loading performance.

Configured in two simple steps:

Add the following entries to package.json:

"scripts": {
  "postbuild": "react-snap"
},
"reactSnap": {
  "skipThirdPartyRequests": true
}

The reactSnap.skipThirdPartyRequests = true entry is critical since it prevents the analytics related requests from executing during static HTML generation. During the build process you may notice the following error logged (per route): Failed to load resource: net::ERR_FAILED. This is a non-issue as it represents the analytics request being intercepted.

And then in src/index.tsx:

import { StrictMode } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
import { hydrateRoot, createRoot } from 'react-dom/client';
import App from './App';

const appElement = (
  <BrowserRouter>
    <HelmetProvider>
      <StrictMode>
        <App />
      </StrictMode>
    </HelmetProvider>
  </BrowserRouter>
);

const container = document.getElementById('root') as HTMLElement;
const hasChildNodes = container?.hasChildNodes() ?? false;

hasChildNodes
  ? hydrateRoot(container, appElement)
  : createRoot(container).render(appElement);

Scripts

npm install

After cloning the repo, run this command. This will:

  • Install Node dependencies from package.json

npm run start

To start the app (development build), run this command. This will:

  • Compile the app and run on the development server

npm run test

  • Execute any Jest tests (based on your configration)

npm run sitemap

  • This command will execute code in the sitemap-generator.js. Using the sitemapUrl parameter defined in that file (should reflect your registered domain name) a sitemap.xml is generated and persisted under the 'public' folder - this file is referenced in the robots.txt file. This uses the sitemap-generator package.

npm run build

This script will:

  • Build release Webpack bundles and run react-snapshot to serve prerendered static files

react-seo-friendly-spa-template's People

Contributors

based-ghost avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

react-seo-friendly-spa-template's Issues

SEO Problem

Hello, I'm creating a blog, and I have a problem with the fetch... when making the request to the backend (laravel) when it returns the response, the page takes a while to load (I even added a "loading" but when I looked at Google ... some pages were positioned well and others say "Loading" hahha

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.