Improve SEO in nginx.conf, index.html; update favicon and logos; embedded card now supported; Dockerfile slimmed and modernized
All checks were successful
/ image_build (push) Successful in 1m14s
All checks were successful
/ image_build (push) Successful in 1m14s
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
.idea
|
.idea
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
.env*
|
.env*
|
||||||
|
.github
|
||||||
|
.git
|
||||||
|
|||||||
33
.editorconfig
Normal file
33
.editorconfig
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Universal settings for all file types
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
max_line_length = 120
|
||||||
|
tab_width = 2
|
||||||
|
|
||||||
|
# React & TypeScript
|
||||||
|
[*.{ts,tsx,js,jsx}]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
# Sass & CSS
|
||||||
|
[*.{sass,scss,css}]
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Webpack configs, Node scripts, and Data files
|
||||||
|
[*.{json,jsonc,yml,yaml,cjs,mjs}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# HTML templates
|
||||||
|
[*.{htm,html}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Markdown overrides
|
||||||
|
[*.{md,markdown,mdc}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
33
Dockerfile
33
Dockerfile
@@ -1,21 +1,24 @@
|
|||||||
FROM node:latest AS build
|
FROM docker.io/node:alpine AS build
|
||||||
WORKDIR /blog
|
WORKDIR /blog
|
||||||
COPY package.json /blog/
|
|
||||||
RUN npm install
|
COPY package.json package-lock.json ./
|
||||||
COPY . /blog/
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM docker.io/nginx:trixie
|
FROM docker.io/nginx:alpine-slim
|
||||||
RUN useradd tymur999 -u 3000
|
|
||||||
RUN chown tymur999:tymur999 -R /etc/logrotate.d/nginx \
|
RUN addgroup -g 3000 tymur999 && \
|
||||||
/etc/nginx \
|
adduser -u 3000 -G tymur999 -S -D tymur999 && \
|
||||||
/etc/init.d/nginx \
|
mkdir -p /var/www/html && \
|
||||||
/usr/lib/nginx \
|
chown -R tymur999:tymur999 /var/cache/nginx /var/log/nginx /etc/nginx/conf.d /var/www/html && \
|
||||||
/usr/share/nginx \
|
touch /tmp/nginx.pid && \
|
||||||
/var/cache/nginx \
|
chown tymur999:tymur999 /tmp/nginx.pid
|
||||||
/var/log/nginx
|
|
||||||
USER tymur999:tymur999
|
USER tymur999:tymur999
|
||||||
|
|
||||||
COPY --from=build --chown=tymur999:tymur999 /blog/build/ /var/www/html/
|
COPY --from=build --chown=tymur999:tymur999 /blog/build/ /var/www/html/
|
||||||
COPY --link nginx.conf /etc/nginx/
|
COPY --chown=tymur999:tymur999 nginx.conf /etc/nginx/nginx.conf
|
||||||
EXPOSE 8000
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|||||||
87
nginx.conf
87
nginx.conf
@@ -8,75 +8,56 @@ events {
|
|||||||
|
|
||||||
http {
|
http {
|
||||||
include mime.types;
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
sendfile on;
|
sendfile on;
|
||||||
tcp_nopush on;
|
tcp_nopush on;
|
||||||
# Prevent nginx HTTP Server Detection
|
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
|
||||||
keepalive_timeout 65;
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# Enable compression on all routes
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_comp_level 5;
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_types application/atom+xml text/javascript application/javascript application/json application/manifest+json application/xml font/opentype image/svg+xml text/css text/plain;
|
||||||
|
|
||||||
|
# Core Security Headers (Applies to all routes unless overridden)
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Robots-Tag "index, follow, max-image-preview:large, max-snippet:-1" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
server_name tymur999.com;
|
server_name tymur999.com;
|
||||||
listen 8000;
|
listen 8000;
|
||||||
|
|
||||||
# set max upload size and increase upload timeout:
|
|
||||||
client_max_body_size 10G;
|
|
||||||
client_body_timeout 300s;
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
gzip_vary on;
|
|
||||||
gzip_comp_level 4;
|
|
||||||
gzip_min_length 256;
|
|
||||||
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
|
|
||||||
gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
|
|
||||||
|
|
||||||
# HTTP response headers borrowed from Nextcloud `.htaccess`
|
|
||||||
add_header Referrer-Policy "no-referrer" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
|
||||||
add_header X-Robots-Tag "noindex, nofollow" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
|
|
||||||
# Path to the root of your installation
|
|
||||||
root /var/www/html;
|
root /var/www/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html =404;
|
||||||
|
# Never cache the entry point so users always get the latest bundle pointers
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Static Assets, WebGL/3D Models, and Fonts
|
||||||
|
location ~* \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac|gltf|glb|otf|ttf|woff2?)$ {
|
||||||
|
try_files $uri =404; # Fail fast on missing assets, do not serve index.html
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# Aggressive caching for hashed bundle assets
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
location = /robots.txt {
|
location = /robots.txt {
|
||||||
allow all;
|
allow all;
|
||||||
log_not_found off;
|
log_not_found off;
|
||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Serve static files
|
|
||||||
location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac)$ {
|
|
||||||
try_files $uri @index;
|
|
||||||
add_header Cache-Control "public, max-age=15778463";
|
|
||||||
add_header Referrer-Policy "no-referrer" always;
|
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
||||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
|
||||||
add_header X-Robots-Tag "noindex, nofollow" always;
|
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.(otf|woff2?)$ {
|
|
||||||
try_files $uri @index;
|
|
||||||
expires 7d; # Cache-Control policy borrowed from `.htaccess`
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
try_files $uri @index;
|
|
||||||
}
|
|
||||||
|
|
||||||
location @index {
|
|
||||||
add_header Cache-Control no-cache;
|
|
||||||
expires 0;
|
|
||||||
try_files /index.html =404;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1087
package-lock.json
generated
1087
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -57,19 +57,23 @@
|
|||||||
"babel-loader": "^10.0.0",
|
"babel-loader": "^10.0.0",
|
||||||
"babel-preset-react-app": "^10.1.0",
|
"babel-preset-react-app": "^10.1.0",
|
||||||
"browserslist": "^4.28.1",
|
"browserslist": "^4.28.1",
|
||||||
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
"css-loader": "^7.1.4",
|
"css-loader": "^7.1.4",
|
||||||
"css-minimizer-webpack-plugin": "^8.0.0",
|
"css-minimizer-webpack-plugin": "^8.0.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
||||||
"html-webpack-plugin": "^5.6.6",
|
"html-webpack-plugin": "^5.6.6",
|
||||||
|
"image-minimizer-webpack-plugin": "^5.0.0",
|
||||||
"mini-css-extract-plugin": "^2.10.0",
|
"mini-css-extract-plugin": "^2.10.0",
|
||||||
"postcss": "^8.5.8",
|
"postcss": "^8.5.8",
|
||||||
"postcss-loader": "^8.2.1",
|
"postcss-loader": "^8.2.1",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.97.3",
|
||||||
"sass-loader": "^16.0.7",
|
"sass-loader": "^16.0.7",
|
||||||
"sass-migrator": "^2.5.7",
|
"sass-migrator": "^2.5.7",
|
||||||
|
"sharp": "^0.35.4",
|
||||||
"source-map-loader": "^5.0.0",
|
"source-map-loader": "^5.0.0",
|
||||||
"style-loader": "^4.0.0",
|
"style-loader": "^4.0.0",
|
||||||
|
"svgo": "^4.1.0",
|
||||||
"terser-webpack-plugin": "^5.3.17",
|
"terser-webpack-plugin": "^5.3.17",
|
||||||
"ts-loader": "^9.5.4",
|
"ts-loader": "^9.5.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|||||||
@@ -4,13 +4,31 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
|
||||||
name="description"
|
<!-- Core SEO -->
|
||||||
content="Tymur Arsentiev's portfolio and blog site"
|
<title>Tymur Arsentiev - Blog & Portfolio</title>
|
||||||
/>
|
<meta name="description" content="Tymur Arsentiev's portfolio and blog site" />
|
||||||
|
<meta name="author" content="Tymur Arsentiev" />
|
||||||
|
<link rel="canonical" href="https://tymur999.com/" />
|
||||||
|
|
||||||
|
<!-- Open Graph / Social Sharing (Facebook, LinkedIn, Discord, etc.) -->
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://tymur999.com/" />
|
||||||
|
<meta property="og:title" content="Tymur Arsentiev - Portfolio & Blog" />
|
||||||
|
<meta property="og:description" content="Tymur Arsentiev's portfolio and blog site" />
|
||||||
|
<meta property="og:image" content="https://tymur999.com/logo192.png" />
|
||||||
|
|
||||||
|
<!-- Twitter Cards -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:url" content="https://tymur999.com/" />
|
||||||
|
<meta name="twitter:title" content="Tymur Arsentiev - Portfolio & Blog" />
|
||||||
|
<meta name="twitter:description" content="Tymur Arsentiev's portfolio and blog site" />
|
||||||
|
<meta name="twitter:image" content="https://tymur999.com/logo192.png" />
|
||||||
|
|
||||||
|
<!-- Existing App Manifest & Icons -->
|
||||||
|
<link rel="icon" href="/logo512.png" />
|
||||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<title>tymur999.com</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 454 KiB After Width: | Height: | Size: 230 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"short_name": "tymur999.com",
|
"short_name": "tymur999.com",
|
||||||
"name": "Tymur Arsentiev's blog",
|
"name": "Tymur Arsentiev's blog and portfolio",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Allow: /
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 15 KiB |
@@ -7,6 +7,8 @@ import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
|||||||
import TerserPlugin from "terser-webpack-plugin";
|
import TerserPlugin from "terser-webpack-plugin";
|
||||||
import {Options as MDXOptions} from '@mdx-js/loader';
|
import {Options as MDXOptions} from '@mdx-js/loader';
|
||||||
import "webpack-dev-server";
|
import "webpack-dev-server";
|
||||||
|
import CopyWebpackPlugin from "copy-webpack-plugin";
|
||||||
|
import ImageMinimizerPlugin, {type CustomOptions, Minimizer} from "image-minimizer-webpack-plugin";
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === "production";
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
@@ -15,7 +17,21 @@ const config: webpack.Configuration = {
|
|||||||
entry: "./src/index.tsx",
|
entry: "./src/index.tsx",
|
||||||
optimization: {
|
optimization: {
|
||||||
minimize: isProduction,
|
minimize: isProduction,
|
||||||
minimizer: [new TerserPlugin()]
|
minimizer: [
|
||||||
|
new TerserPlugin(),
|
||||||
|
// use Sharp lossless image compression
|
||||||
|
new ImageMinimizerPlugin({
|
||||||
|
minimizer: {
|
||||||
|
implementation: ImageMinimizerPlugin.sharpMinify,
|
||||||
|
options: {
|
||||||
|
encodeOptions: {
|
||||||
|
jpeg: { quality: 80 },
|
||||||
|
png: { compressionLevel: 8 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as Minimizer<CustomOptions>
|
||||||
|
})
|
||||||
|
]
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(import.meta.dirname, "build"),
|
path: path.resolve(import.meta.dirname, "build"),
|
||||||
@@ -51,9 +67,13 @@ const config: webpack.Configuration = {
|
|||||||
loader: "@svgr/webpack",
|
loader: "@svgr/webpack",
|
||||||
options: {
|
options: {
|
||||||
prettier: false,
|
prettier: false,
|
||||||
svgo: false,
|
svgo: true,
|
||||||
svgoConfig: {
|
svgoConfig: {
|
||||||
plugins: [{ removeViewBox: false }],
|
multipass: true,
|
||||||
|
plugins: [
|
||||||
|
'preset-default',
|
||||||
|
'prefixIds'
|
||||||
|
],
|
||||||
},
|
},
|
||||||
titleProp: true,
|
titleProp: true,
|
||||||
ref: true,
|
ref: true,
|
||||||
@@ -140,7 +160,18 @@ const config: webpack.Configuration = {
|
|||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: path.resolve(import.meta.dirname, "public/index.html"),
|
template: path.resolve(import.meta.dirname, "public/index.html"),
|
||||||
favicon: path.resolve(import.meta.dirname, "src/img/favicon.ico"),
|
favicon: path.resolve(import.meta.dirname, "src/img/favicon.ico"),
|
||||||
inject: true,
|
inject: true
|
||||||
|
}),
|
||||||
|
// copy public files to build dir
|
||||||
|
new CopyWebpackPlugin({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
from: 'public',
|
||||||
|
to: '.',
|
||||||
|
// html file is handled by HtmlWebpackPlugin
|
||||||
|
globOptions: { ignore: ['**/index.html'] }
|
||||||
|
}
|
||||||
|
]
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env": JSON.stringify(process.env),
|
"process.env": JSON.stringify(process.env),
|
||||||
@@ -165,4 +196,4 @@ const config: webpack.Configuration = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
Reference in New Issue
Block a user