Home
Softono
b

based-ghost

Professional software vendor delivering innovative solutions on the Softono platform. Specialized in both open-source and proprietary software development.

Total Products
2

Software by based-ghost

react-seo-friendly-spa-template
Open Source

react-seo-friendly-spa-template

# react-seo-friendly-spa-template React PWA/SPA template configured for SEO (initially scaffolded with Create React App). Features: - TypeScript - Incorporates [`styled-components`](https://github.com/styled-components/styled-components) - Route transitions handled using [`react-transition-group`](https://github.com/reactjs/react-transition-group) - Built entirely with `React Hooks` API (no legacy class components) - Google analytics management with [`react-ga`](https://github.com/react-ga/react-ga) - Route meta tag management with [`react-helmet-async`](https://github.com/staylor/react-helmet-async) - Configured to serve prerendered static HTML with [`react-snap`](https://github.com/stereobooster/react-snap) - Custom `BackToTop.tsx` component - Custom `ToggleTheme.tsx` component that handles light/dark theme transitions ## Demo ![demo](./demo/react_seo_friendly_demo.gif) ## General Overview This is the React version based on my Vue SEO template which you can find here: [vue-seo-friendly-spa-template](https://github.com/based-ghost/vue-seo-friendly-spa-template) ## Technology Stack Overview ### Create React App initial scaffolding ### react-helmet-async [`react-helmet-async`](https://github.com/staylor/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`](https://github.com/nfl/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` ```jsx 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 ```jsx 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`](https://github.com/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`: ```jsx 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 ```jsx 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`](https://github.com/stereobooster/react-snap) - 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`: ```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`: ```jsx 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

Frontend Templates SEO Tools
52 Github Stars
vue-seo-friendly-spa-template
Open Source

vue-seo-friendly-spa-template

# vue-seo-friendly-spa-template Built using Vue 3.0. Vue.js PWA/SPA template configured for SEO (initially scaffolded with vue-cli). You can find the React version here: [react-seo-friendly-spa-template](https://github.com/based-ghost/react-seo-friendly-spa-template). Features: - TypeScript - Custom `BackToTop.vue` component that uses [`vue-scrollto`](https://github.com/rigor789/vue-scrollto) - Custom `ToggleTheme.vue` component that handles light/dark theme transitions - Google analytics management with [`vue-gtag-next`](https://github.com/MatteoGabriele/vue-gtag-next) - Route meta tag management with [`vue-meta`](https://github.com/nuxt/vue-meta/tree/next) - Configured to serve prerendered static HTML with [`prerender-spa-plugin`](https://github.com/chrisvfritz/prerender-spa-plugin) ## Demo ![demo](./demo/vue_seo_friendly_demo.gif) ## General Overview This template reflects some of the setup I went through when experimenting with the creation of my own static front-end personal site that was to be hosted on Netlify (using GitHub as a repository/pipeline). You can find that experiment live [here](https://basedghostdevelopment.com). After playing around with this process I figured I'd build a higher-level abstraction of that project for quick re-use in the future. ## Technology Stack Overview ### vue-cli initial scaffolding ### vue-meta [`vue-meta`](https://github.com/nuxt/vue-meta/tree/next) - plugin that allows you to manage your app's meta information, much like [`react-helmet`](https://github.com/nfl/react-helmet) does for React. However, instead of setting your data as props passed to a proprietary component, you simply export it as part of your component's data using the metaInfo property. I have meta data configured to be handled via a simple, reusable compostion (`@/composables/useMetaRoute.ts`) - simply import and execute this composable function in the `setup` function of your component and it will attempt to resolve any meta data definitions you configure for that route: `useMetaRoute.ts` ```typescript import { useRoute } from 'vue-router'; import { useMeta, type MetaSourceProxy } from 'vue-meta'; export default function useMetaRoute(): MetaSourceProxy { const route = useRoute(); const { title, description } = route?.meta ?? {}; const url = window?.location.href || 'unknown'; const { meta } = useMeta({ title, description, link: { rel: 'canonical', href: url }, og: { url, title, description } }); return meta; } ``` `About.vue` ```typescript <script setup lang="ts"> import { Alert } from '@/components'; import { useMetaRoute } from '@/composables'; useMetaRoute(); </script> ``` ### vue-gtag-next [`vue-gtag-next`](https://github.com/MatteoGabriele/vue-gtag-next) - The global site tag (gtag.js) is a JavaScript tagging framework and API that allows you to send event data to Google Analytics, Google Ads, and Google Marketing Platform. Inititial plugin configuration found in `config/vue-gtag.config.ts` and then hooked up in the setup function of the application's root component (`App.vue`). `vue-gtag.config.ts` ```typescript import type { Options } from 'vue-gtag-next'; const isEnabled = true; const isProduction = process.env.NODE_ENV === 'production'; const useDebugger = isEnabled && !isProduction; export const VUE_GTAG_OPTIONS: Options = { isEnabled, useDebugger, property: { id: 'UA-000000-01', params: { send_page_view: false, } } }; ``` `App.vue` ```typescript <script setup lang="ts"> import { watch, unref } from 'vue'; import { useRouter } from 'vue-router'; import { useGtag } from 'vue-gtag-next'; import { useActiveMeta } from 'vue-meta'; const router = useRouter(); const { pageview } = useGtag(); const activeMeta = useActiveMeta(); function trackPageView() { setTimeout(() => { const { currentRoute, getRoutes } = router; const { path } = unref(currentRoute); const isValidPath = getRoutes().some((x) => x.path === path); if (isValidPath) { pageview(path); } }, 10); } watch( () => activeMeta, () => trackPageView(), { deep: true } ); </script> ``` ### prerender-spa-plugin [`prerender-spa-plugin`](https://github.com/chrisvfritz/prerender-spa-plugin) - Prerenders static HTML in a single-page application. This is a more straightforward substitue for SSR (Server Side Rendering) and the primary benefit is SEO. Configured in the app as follows: `vue.config.js` ```javascript const path = require("path"); const cheerio = require("cheerio"); const PrerenderSPAPlugin = require("prerender-spa-plugin-next"); const PuppeteerRenderer = require("@prerenderer/renderer-puppeteer"); module.exports = { lintOnSave: false, // define port devServer: { port: "3000", hot: true, }, configureWebpack: (config) => { if (process.env.NODE_ENV !== "production") { return {}; } return { performance: { hints: false, }, plugins: [ // https://github.com/chrisvfritz/prerender-spa-plugin new PrerenderSPAPlugin({ staticDir: config.output.path, routes: ["/", "/about"], renderer: PuppeteerRenderer, postProcess(context) { if (context.route === "/404") { context.outputPath = path.join(config.output.path, "/404.html"); } // Add 'data-server-rendered' attribute so app knows to hydrate with any changes const $ = cheerio.load(context.html); $("#app").attr("data-server-rendered", "true"); context.html = $.html(); return context; }, }), ], }; } }; ``` Remainder of the configuration takes place in `vue.config.js` file where the plugin is added and configured. In the `postProcess` callback I am editing the prerendered content using `cheerio` so you can load the raw prerendered html string into a usable document and modify it using JQuery-like syntax, rather than parsing a long string and calling `.replace()`. <strong>Note:</strong> I found that dynamically adding the `data-server-rendered='true'` attribute in the `postProcess` (rather than hard-coding in the index.html file) seems to work well - this lets the client know that this nodes contents was served as prerendered content and to hydrate the HTML with updates, rather than re-render/replace. ## Scripts ## Project setup ``` npm install ``` ### Compiles and hot-reloads for development ``` npm run serve ``` ### Compiles and minifies for production ``` npm run build ``` ### Lints and fixes files ``` npm run lint ``` - Run the linter (configured in the tslint.json file found in the root of this project) ### Generate sitemap.xml file ``` 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.

Web Development Frontend Templates SEO Tools
46 Github Stars