Skip to content

blog: switch to React from Jekyll#37

Merged
gapry merged 7 commits intomainfrom
react-init
Mar 5, 2026
Merged

blog: switch to React from Jekyll#37
gapry merged 7 commits intomainfrom
react-init

Conversation

@gapry
Copy link
Owner

@gapry gapry commented Mar 5, 2026

No description provided.

Copy link

@amazon-q-developer amazon-q-developer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary: Migration from Jekyll to React

This PR migrates the blog from Jekyll to React with Vite. While the migration approach is sound, there are critical blocking issues that must be addressed before merge:

Critical Issues (Must Fix)

  1. ESLint Configuration Errors: Invalid imports and syntax that will prevent linting from working
  2. GitHub Actions Deployment: Missing authentication token will cause deployment failures
  3. Analytics Implementation: Logic error in gtag function implementation will break tracking
  4. Runtime Safety: Missing error handling in main.jsx could cause application crashes
  5. Security: Hardcoded tracking ID should be moved to environment variables

All critical issues have been commented inline with specific fixes. Please address these before merging to ensure the site deploys and functions correctly.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.


⚠️ This PR contains more than 30 files. Amazon Q is better at reviewing smaller PRs, and may miss issues in larger changesets.

"siteName": "Gapry's Technical Writing",
"analytics": {
"provider": "google-gtag",
"trackingId": "G-4DG3HSBT1T",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Hardcoded Google Analytics tracking ID is committed to the repository. This tracking ID should be treated as sensitive configuration and moved to environment variables to prevent unauthorized usage and potential data collection hijacking.

    "trackingId": import.meta.env.VITE_GA_TRACKING_ID,

Comment on lines +34 to +38
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist
branch: gh-pages No newline at end of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Deployment Failure: Missing authentication token for GitHub Pages deployment. The JamesIves/github-pages-deploy-action@v4 requires a token parameter for authentication, or the workflow will fail during deployment.

Suggested change
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist
branch: gh-pages
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
folder: dist
branch: gh-pages

Comment on lines +6 to +10
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Crash Risk: Missing null check for DOM element. If the 'root' element doesn't exist in the HTML, createRoot will throw an error and crash the application. Add error handling to prevent runtime failure.

Suggested change
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element not found. Please ensure index.html contains a div with id="root".');
}
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>,
)

Comment on lines +22 to +24
window.gtag = function() {
window.dataLayer.push(arguments);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Unsafe use of arguments in arrow function. Arrow functions don't have their own arguments object, causing this gtag implementation to fail. Use rest parameters instead to properly forward arguments to dataLayer.push.

Suggested change
window.gtag = function() {
window.dataLayer.push(arguments);
};
window.gtag = function(...args) {
window.dataLayer.push(args);
};

import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Syntax Error: Invalid import from 'eslint/config' - this module doesn't exist in the eslint package. Remove this import and the defineConfig usage, or use the correct ESLint flat config export format. This will cause configuration loading to fail.

import { defineConfig, globalIgnores } from 'eslint/config'

Comment on lines +9 to +28
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Syntax Error: Invalid usage of extends property in ESLint flat config format. The flat config format doesn't support the extends property - configs should be spread directly into the array. This will cause ESLint to fail with a configuration error.

Suggested change
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
{
files: ['**/*.{js,jsx}'],
...js.configs.recommended,
...reactHooks.configs.flat.recommended,
...reactRefresh.configs.vite,
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request undertakes a significant architectural change by migrating the blog from a Jekyll-based static site generator to a dynamic React application using Vite. This transition modernizes the underlying technology stack, moving from Ruby-based templating to a JavaScript component-driven approach. The primary goal is to enhance maintainability, improve development experience, and provide a more flexible foundation for future features and content delivery.

Highlights

  • Platform Migration: The blog platform has been completely migrated from Jekyll to a modern React application powered by Vite.
  • Project Structure Overhaul: All Jekyll-specific configuration files, layouts, posts, and scripts have been removed, replaced by a new JavaScript-centric project structure including package.json, vite.config.js, and React source files.
  • Development Tooling Updates: The .gitignore file was updated to reflect Node.js and editor-specific exclusions, and VS Code tasks were reconfigured to use npm commands for development workflows.
  • Initial React Application Setup: A basic React application with Google Analytics integration, essential styling, and ESLint configuration has been introduced, laying the groundwork for future content.
Changelog
  • .gitignore
    • Updated to include common Node.js, Vite, and editor-specific ignore patterns.
    • Removed Jekyll-specific ignore patterns such as _site/, .sass-cache/, and vendor/.
  • .vscode/tasks.json
    • Modified existing tasks to use npm install and npm run build instead of custom shell scripts.
    • Added a new task npm run dev for starting the development server.
  • 404.html
    • Removed the Jekyll-specific 404 error page.
  • Gemfile
    • Removed the Ruby Gemfile, which managed Jekyll dependencies.
  • Gemfile.lock
    • Removed the Ruby Gemfile.lock, which specified exact Jekyll dependency versions.
  • README.md
    • Added a new README.md file with licensing information for technical writing.
  • _config.yml
    • Removed the main Jekyll configuration file.
  • _data/navigation.yml
    • Removed the Jekyll navigation data file.
  • _includes/google-analytics.html
    • Removed the Jekyll include for Google Analytics.
  • _includes/head/custom.html
    • Removed the Jekyll include for custom head content.
  • _layouts/home.html
    • Removed the Jekyll home page layout.
  • _posts/2026-01-01-Advent-of-Compiler-Optimisations-Study-Notes-01.md
    • Removed the Jekyll blog post content.
  • _posts/2026-01-31-Advent-of-Compiler-Optimisations-Study-Notes-02.md
    • Removed the Jekyll blog post content.
  • _posts/2026-01-31-Advent-of-Compiler-Optimisations-Study-Notes-03.md
    • Removed the Jekyll blog post content.
  • _posts/2026-02-27-Advent-of-Compiler-Optimisations-Study-Notes-04.md
    • Removed the Jekyll blog post content.
  • _posts/2026-02-28-Advent-of-Compiler-Optimisations-Study-Notes-05.md
    • Removed the Jekyll blog post content.
  • _posts/2026-02-28-Advent-of-Compiler-Optimisations-Study-Notes-06.md
    • Removed the Jekyll blog post content.
  • about.md
    • Removed the Jekyll about page content.
  • eslint.config.js
    • Added a new ESLint configuration file for JavaScript/React code quality.
  • index.html
    • Added the main HTML entry point for the React application.
  • index.md
    • Removed the Jekyll index page.
  • package.json
    • Added a new package.json file, defining project metadata, scripts, and JavaScript dependencies for React and Vite.
  • public/manifest.json
    • Added a web app manifest file for progressive web app features.
  • public/robots.txt
    • Added a robots.txt file to control search engine crawling.
  • scripts/install.sh
    • Removed the custom shell script for local installation.
  • scripts/test.sh
    • Removed the custom shell script for local testing.
  • src/Analytics.jsx
    • Added a React component for integrating Google Analytics.
  • src/App.css
    • Added global CSS styling for the React application.
  • src/App.jsx
    • Added the main React application component, including a basic 'Hello World' display and Analytics integration.
  • src/config.json
    • Added a JSON configuration file for site-wide settings like analytics and social links.
  • src/index.css
    • Added base CSS styling for the HTML body and dark mode preferences.
  • src/main.jsx
    • Added the entry point for the React application, responsible for rendering the root component.
  • vite.config.js
    • Added a Vite configuration file for building and serving the React application.
Ignored Files
  • Ignored by pattern: .gemini/** (1)
    • .gemini/config.yaml
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/gh-pages.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request marks a significant migration of the blog's technical stack from Jekyll to React with Vite. The changes involve removing the old Jekyll structure and introducing a new, modern frontend setup. This includes configurations for development tools like Vite and ESLint, and a basic application structure.

While the new foundation is well-established, I've identified a critical issue in the public/robots.txt file. It contains what appears to be a shell command instead of the actual directives, which will make it invalid for search engine crawlers.

Additionally, this PR removes all existing blog content. It would be helpful to include a brief note in the PR description outlining the strategy for content migration to the new platform. Overall, this is a good foundational step for the new blog architecture.

Comment on lines +1 to +2
echo "User-agent: *
Allow: /" > public/robots.txt No newline at end of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The content of robots.txt appears to be a shell command instead of the actual robots.txt rules. The file should contain the rules directly, not the command used to generate them. This will result in an invalid robots.txt file on the deployed site, which can negatively impact SEO by preventing search engines from crawling your site.

User-agent: *
Allow: /

@gapry
Copy link
Owner Author

gapry commented Mar 5, 2026

/gemini review

@gapry
Copy link
Owner Author

gapry commented Mar 5, 2026

/q review

Copy link

@amazon-q-developer amazon-q-developer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR successfully migrates from Jekyll to React, but several critical issues must be addressed before merge:

Critical Issues Found (6)

  1. Security: Hardcoded Google Analytics tracking ID in config.json should be externalized to environment variables
  2. Security: XSS vulnerability in Analytics.jsx - trackingId needs sanitization with encodeURIComponent
  3. Memory Leak: Missing cleanup function in Analytics useEffect causes script tag accumulation on remount
  4. Crash Risk: Missing null check for root element in main.jsx will crash app if DOM element not found
  5. Configuration Error: ESLint flat config using invalid extends property will cause linting failures
  6. Deployment Logic: GitHub Actions workflow deploys on every PR, risking production overwrites with unreviewed code

Migration Assessment

The core migration approach is sound - switching from Jekyll to Vite + React is appropriate for a modern blog platform. The build pipeline and dependencies are properly configured. However, the security vulnerabilities and logic errors identified above must be fixed to ensure reliable operation.

All comments include specific code suggestions that can be applied directly from the GitHub UI.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.


⚠️ This PR contains more than 30 files. Amazon Q is better at reviewing smaller PRs, and may miss issues in larger changesets.

"siteName": "Gapry's Technical Writing",
"analytics": {
"provider": "google-gtag",
"trackingId": "G-4DG3HSBT1T",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Hardcoded Google Analytics tracking ID should be externalized to environment variables to prevent unauthorized usage and enable different configurations per environment.1

Footnotes

  1. CWE-798: Use of Hard-coded Credentials - https://cwe.mitre.org/data/definitions/798.html

const script = document.createElement('script');

script.id = 'google-analytics';
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Template literal injection risk - the trackingId from config.json is directly interpolated into the script URL without validation or sanitization, creating an XSS vulnerability if the config file is compromised.1

Suggested change
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
script.src = `

Footnotes

  1. CWE-79: Cross-site Scripting (XSS) - https://cwe.mitre.org/data/definitions/79.html

Comment on lines +7 to +30
useEffect(() => {
if (!trackingId || document.getElementById('google-analytics')) {
return;
}

const script = document.createElement('script');

script.id = 'google-analytics';
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
script.async = true;

document.head.appendChild(script);

window.dataLayer = window.dataLayer || [];

window.gtag = function() {
window.dataLayer.push(arguments);
};

window.gtag('js', new Date());
window.gtag('config', trackingId, {
anonymize_ip: siteConfig.analytics.anonymizeIp
});
}, [trackingId]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Missing cleanup function in useEffect causes memory leak - the dynamically created script element is never removed when the component unmounts, leading to duplicate script tags and potential tracking issues on component remount.

Suggested change
useEffect(() => {
if (!trackingId || document.getElementById('google-analytics')) {
return;
}
const script = document.createElement('script');
script.id = 'google-analytics';
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
script.async = true;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
window.gtag = function() {
window.dataLayer.push(arguments);
};
window.gtag('js', new Date());
window.gtag('config', trackingId, {
anonymize_ip: siteConfig.analytics.anonymizeIp
});
}, [trackingId]);
useEffect(() => {
if (!trackingId || document.getElementById('google-analytics')) {
return;
}
const script = document.createElement('script');
script.id = 'google-analytics';
script.src = `
script.async = true;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
window.gtag = function() {
window.dataLayer.push(arguments);
};
window.gtag('js', new Date());
window.gtag('config', trackingId, {
anonymize_ip: siteConfig.analytics.anonymizeIp
});
return () => {
const scriptElement = document.getElementById('google-analytics');
if (scriptElement) {
scriptElement.remove();
}
};
}, [trackingId]);

Comment on lines +6 to +10
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Crash Risk: Missing null check - if the 'root' element doesn't exist in the DOM, createRoot will throw an error causing the application to crash on page load.

Suggested change
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element not found');
}
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>,
)

Comment on lines +9 to +28
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: The extends property is invalid in ESLint flat config format (v9+) and will cause linting to fail. Flat config requires spreading the configs directly into the configuration object instead of using the extends property.

Suggested change
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
{
files: ['**/*.{js,jsx}'],
...js.configs.recommended,
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},

Comment on lines +15 to 16
build-and-deploy:
runs-on: ubuntu-latest

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: The workflow deploys to gh-pages branch on every pull request, which will overwrite production with unreviewed code. Deployment should only occur on push to main branch after PR approval.

Suggested change
build-and-deploy:
runs-on: ubuntu-latest
build-and-deploy:
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the blog from Jekyll to a modern React setup using Vite. The changes involve removing all Jekyll-related files and introducing a standard Vite + React project structure. Overall, the migration is a significant step forward. I've identified a critical issue in the ESLint configuration that will prevent it from working, a high-severity bug in the Google Analytics component logic, and a medium-severity style issue.

Comment on lines +7 to +29
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Your ESLint configuration appears to be using the extends property within a flat config object, which is not supported in ESLint's new flat configuration format. In flat config, configurations are composed as an array of objects. Additionally, globalIgnores returns an array and should be spread into the main configuration array.

This will cause ESLint to fail. Here is a corrected version that follows the flat config structure:

export default defineConfig([
  ...globalIgnores(['dist']),
  js.configs.recommended,
  reactHooks.configs.flat.recommended,
  reactRefresh.configs.vite,
  {
    files: ['**/*.{js,jsx}'],
    languageOptions: {
      ecmaVersion: 'latest',
      globals: globals.browser,
      parserOptions: {
        ecmaFeatures: { jsx: true },
        sourceType: 'module',
      },
    },
    rules: {
      'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
    },
  },
])

Comment on lines +7 to +30
useEffect(() => {
if (!trackingId || document.getElementById('google-analytics')) {
return;
}

const script = document.createElement('script');

script.id = 'google-analytics';
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
script.async = true;

document.head.appendChild(script);

window.dataLayer = window.dataLayer || [];

window.gtag = function() {
window.dataLayer.push(arguments);
};

window.gtag('js', new Date());
window.gtag('config', trackingId, {
anonymize_ip: siteConfig.analytics.anonymizeIp
});
}, [trackingId]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This useEffect has a potential bug. If the trackingId changes, the effect will re-run, but because the script element with id='google-analytics' already exists, the function will return early without configuring the new trackingId. Also, window.gtag is only defined when the script is first injected, which can lead to issues on subsequent renders.

A better approach is to always ensure gtag is configured and only inject the script once. The anonymizeIp property should also be included in the dependency array.

  useEffect(() => {
    if (!trackingId) {
      return;
    }

    // Ensure dataLayer and gtag stub are available.
    window.dataLayer = window.dataLayer || [];
    if (typeof window.gtag !== 'function') {
      window.gtag = function() {
        window.dataLayer.push(arguments);
      };
      window.gtag('js', new Date());
    }

    // Always configure on trackingId change.
    window.gtag('config', trackingId, {
      anonymize_ip: siteConfig.analytics.anonymizeIp,
    });

    // Inject the script only if it doesn't exist.
    if (document.getElementById('google-analytics')) {
      return;
    }

    const script = document.createElement('script');
    script.id = 'google-analytics';
    script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
    script.async = true;
    document.head.appendChild(script);
  }, [trackingId, siteConfig.analytics.anonymizeIp]);

@@ -0,0 +1 @@
My Technical Writing Licensed under **CC BY-NC-SA 4.0**, see [LICENSE](LICENSE) file. No newline at end of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It's a good practice to end files with a newline character. Many tools and conventions expect this, and it can prevent issues with file concatenation or processing.

Suggested change
My Technical Writing Licensed under **CC BY-NC-SA 4.0**, see [LICENSE](LICENSE) file.
My Technical Writing Licensed under **CC BY-NC-SA 4.0**, see [LICENSE](LICENSE) file.

@gapry
Copy link
Owner Author

gapry commented Mar 5, 2026

/gemini review

@gapry
Copy link
Owner Author

gapry commented Mar 5, 2026

/q review

Copy link

@amazon-q-developer amazon-q-developer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR migrates from Jekyll to React with Vite and Bun, which is a significant architectural change. However, there are 4 critical issues that must be resolved before merging:

Critical Issues Found:

  1. Security: Hardcoded Google Analytics tracking ID in source code (should use environment variables)
  2. Build Breaking: Invalid ESLint import path (eslint/config doesn't exist in ESLint v9)
  3. Syntax Error: Missing closing bracket in ESLint configuration
  4. Runtime Error: Missing error handling for Google Analytics script loading

Required Actions:

  • Move the tracking ID to environment variables (.env file with VITE_GA_TRACKING_ID)
  • Fix ESLint configuration to use proper flat config syntax without the invalid import
  • Add error handling for external script loading to prevent silent failures

Once these blocking issues are resolved, the migration should be ready for deployment.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.


⚠️ This PR contains more than 30 files. Amazon Q is better at reviewing smaller PRs, and may miss issues in larger changesets.

'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Missing closing bracket for the configuration array. Since the opening was changed, this closing bracket needs to be updated to properly close the array.

Suggested change
])
]

"siteName": "Gapry's Technical Writing",
"analytics": {
"provider": "google-gtag",
"trackingId": "G-4DG3HSBT1T",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Security Vulnerability: Hardcoded Google Analytics tracking ID should be moved to environment variables. This exposes analytics configuration in version control and makes it difficult to use different tracking IDs across environments (development, staging, production).

Suggested change
"trackingId": "G-4DG3HSBT1T",
"trackingId": import.meta.env.VITE_GA_TRACKING_ID || "",

Comment on lines +5 to +8
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Logic Error: Import path eslint/config does not exist in ESLint v9. This will cause a runtime error when ESLint tries to load the configuration file. The correct approach for ESLint v9 flat config is to not use defineConfig and globalIgnores from this import.

Suggested change
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
export default [
{ ignores: ['dist'] },

Comment on lines +28 to +33
const script = document.createElement('script');
script.id = 'google-analytics';
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
script.async = true;

document.head.appendChild(script);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Crash Risk: Script loading failure is not handled. If the Google Analytics script fails to load (network error, blocked by ad blocker, CSP violation), the error will be silently swallowed. Add error handling to prevent silent failures.

Suggested change
const script = document.createElement('script');
script.id = 'google-analytics';
script.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`;
script.async = true;
document.head.appendChild(script);
const script = document.createElement('script');
script.id = 'google-analytics';
script.src = `
script.async = true;
script.onerror = () => console.warn('Failed to load Google Analytics');
document.head.appendChild(script);

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully migrates the blog from Jekyll to a modern React stack using Vite and Bun. The overall structure is clean and follows common practices for React projects. I've found a critical issue with the package dependencies that will prevent the project from being installed, and I've also suggested an improvement for handling configuration to enhance maintainability. Please see the detailed comments.

Comment on lines +13 to +14
"react": "^19.2.0",
"react-dom": "^19.2.0"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The specified React versions ^19.2.0 for react and react-dom do not appear to be valid, publicly available versions on npm. This will likely cause bun install (or npm install/yarn) to fail for anyone else trying to set up the project.

You should use a valid version string. If you intend to use the latest stable version, you can use ^18.3.1. If you want to use the upcoming React 19, you should use the correct version tag, such as a specific release candidate or an experimental build tag.

Suggested change
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react": "^18.3.1",
"react-dom": "^18.3.1"

"siteName": "Gapry's Technical Writing",
"analytics": {
"provider": "google-gtag",
"trackingId": "G-4DG3HSBT1T",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Hardcoding configuration values like the Google Analytics trackingId directly in the configuration file is not recommended. While this ID is not a secret, using environment variables is a best practice for managing configuration that might differ between environments (e.g., development vs. production). This makes your application more flexible and easier to configure for deployment.

With Vite, you can achieve this by:

  1. Creating a .env.local file in your project root with the content: VITE_GA_TRACKING_ID=G-4DG3HSBT1T
  2. Adding .env.local to your .gitignore file.
  3. Accessing the variable in your code using import.meta.env.VITE_GA_TRACKING_ID.

This approach keeps environment-specific configuration out of your version-controlled code.

@gapry gapry merged commit ae008b7 into main Mar 5, 2026
1 check passed
@gapry gapry deleted the react-init branch March 5, 2026 01:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant