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:
@@ -2,3 +2,5 @@
|
||||
build
|
||||
node_modules
|
||||
.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
|
||||
31
Dockerfile
31
Dockerfile
@@ -1,21 +1,24 @@
|
||||
FROM node:latest AS build
|
||||
FROM docker.io/node:alpine AS build
|
||||
WORKDIR /blog
|
||||
COPY package.json /blog/
|
||||
RUN npm install
|
||||
COPY . /blog/
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM docker.io/nginx:trixie
|
||||
RUN useradd tymur999 -u 3000
|
||||
RUN chown tymur999:tymur999 -R /etc/logrotate.d/nginx \
|
||||
/etc/nginx \
|
||||
/etc/init.d/nginx \
|
||||
/usr/lib/nginx \
|
||||
/usr/share/nginx \
|
||||
/var/cache/nginx \
|
||||
/var/log/nginx
|
||||
FROM docker.io/nginx:alpine-slim
|
||||
|
||||
RUN addgroup -g 3000 tymur999 && \
|
||||
adduser -u 3000 -G tymur999 -S -D tymur999 && \
|
||||
mkdir -p /var/www/html && \
|
||||
chown -R tymur999:tymur999 /var/cache/nginx /var/log/nginx /etc/nginx/conf.d /var/www/html && \
|
||||
touch /tmp/nginx.pid && \
|
||||
chown tymur999:tymur999 /tmp/nginx.pid
|
||||
|
||||
USER tymur999:tymur999
|
||||
|
||||
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
|
||||
85
nginx.conf
85
nginx.conf
@@ -8,75 +8,56 @@ events {
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
# Prevent nginx HTTP Server Detection
|
||||
server_tokens off;
|
||||
|
||||
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_name tymur999.com;
|
||||
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;
|
||||
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 {
|
||||
allow all;
|
||||
log_not_found 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-preset-react-app": "^10.1.0",
|
||||
"browserslist": "^4.28.1",
|
||||
"copy-webpack-plugin": "^14.0.0",
|
||||
"css-loader": "^7.1.4",
|
||||
"css-minimizer-webpack-plugin": "^8.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.1.0",
|
||||
"html-webpack-plugin": "^5.6.6",
|
||||
"image-minimizer-webpack-plugin": "^5.0.0",
|
||||
"mini-css-extract-plugin": "^2.10.0",
|
||||
"postcss": "^8.5.8",
|
||||
"postcss-loader": "^8.2.1",
|
||||
"sass": "^1.97.3",
|
||||
"sass-loader": "^16.0.7",
|
||||
"sass-migrator": "^2.5.7",
|
||||
"sharp": "^0.35.4",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"svgo": "^4.1.0",
|
||||
"terser-webpack-plugin": "^5.3.17",
|
||||
"ts-loader": "^9.5.4",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -4,13 +4,31 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Tymur Arsentiev's portfolio and blog site"
|
||||
/>
|
||||
|
||||
<!-- Core SEO -->
|
||||
<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="manifest" href="/manifest.json" />
|
||||
<title>tymur999.com</title>
|
||||
</head>
|
||||
<body>
|
||||
<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",
|
||||
"name": "Tymur Arsentiev's blog",
|
||||
"name": "Tymur Arsentiev's blog and portfolio",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
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 {Options as MDXOptions} from '@mdx-js/loader';
|
||||
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";
|
||||
|
||||
@@ -15,7 +17,21 @@ const config: webpack.Configuration = {
|
||||
entry: "./src/index.tsx",
|
||||
optimization: {
|
||||
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: {
|
||||
path: path.resolve(import.meta.dirname, "build"),
|
||||
@@ -51,9 +67,13 @@ const config: webpack.Configuration = {
|
||||
loader: "@svgr/webpack",
|
||||
options: {
|
||||
prettier: false,
|
||||
svgo: false,
|
||||
svgo: true,
|
||||
svgoConfig: {
|
||||
plugins: [{ removeViewBox: false }],
|
||||
multipass: true,
|
||||
plugins: [
|
||||
'preset-default',
|
||||
'prefixIds'
|
||||
],
|
||||
},
|
||||
titleProp: true,
|
||||
ref: true,
|
||||
@@ -140,7 +160,18 @@ const config: webpack.Configuration = {
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(import.meta.dirname, "public/index.html"),
|
||||
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({
|
||||
"process.env": JSON.stringify(process.env),
|
||||
|
||||
Reference in New Issue
Block a user