diff --git a/infra/releases/2026-05-30_19-03-06_HML.md b/infra/releases/2026-05-30_19-03-06_HML.md new file mode 100644 index 00000000..a952a93e --- /dev/null +++ b/infra/releases/2026-05-30_19-03-06_HML.md @@ -0,0 +1,31 @@ +# Release HML + +Data: sáb 30 mai 2026 19:03:06 -03 + +Ambiente: +HML + +Descrição: +Correção de sub pastas + +Branch: +develop + +Arquivos alterados antes do commit: + D infra/releases/2026-05-30_16-39-05_HML.md + M public/version.json +?? infra/releases/2026-05-30_19-03-06_HML.md + +Status: +Preparado para publicação + +Commit: +246d860 + +Arquivos no commit: +infra/releases/2026-05-30_16-39-05_HML.md +infra/releases/2026-05-30_19-03-06_HML.md +public/version.json + +Status final: +Publicado no Gitea / aguardando pipeline HML diff --git a/package-lock.json b/package-lock.json index c0855b0d..58fc4421 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "react": "^19.2.0", "react-day-picker": "^9.13.0", "react-dom": "^19.2.0", + "react-helmet-async": "^3.0.0", "react-hook-form": "^7.70.0", "react-resizable-panels": "^4.2.2", "react-router-dom": "^7.14.0", @@ -6744,6 +6745,15 @@ "node": ">=12" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmmirror.com/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7668,6 +7678,26 @@ "react": "^19.2.3" } }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/react-helmet-async/-/react-helmet-async-3.0.0.tgz", + "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==", + "license": "Apache-2.0", + "dependencies": { + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-hook-form": { "version": "7.70.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.70.0.tgz", @@ -8131,6 +8161,12 @@ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "license": "MIT" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index d52ec2a7..c1b49c58 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "react": "^19.2.0", "react-day-picker": "^9.13.0", "react-dom": "^19.2.0", + "react-helmet-async": "^3.0.0", "react-hook-form": "^7.70.0", "react-resizable-panels": "^4.2.2", "react-router-dom": "^7.14.0", diff --git a/public/version.json b/public/version.json index 47ebfb96..8646b67a 100644 --- a/public/version.json +++ b/public/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.9", - "date": "2026-05-29", + "version": "1.1.1", + "date": "2026-05-31", "environment": "development" } diff --git a/scripts/build-static-routes.sh b/scripts/build-static-routes.sh new file mode 100644 index 00000000..d0e90407 --- /dev/null +++ b/scripts/build-static-routes.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# ============================================================================ +# Script Post-Build - Gera paginas estaticas para rotas React +# ============================================================================ +# Isso permite que URLs como /avaliacao funcionem diretamente, +# mesmo em servidores sem try_files configurado. +# +# Uso: bash scripts/build-static-routes.sh (executa automaticamente apos build) +# ============================================================================ + +set -e + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}[Post-Build]${NC} Gerando paginas estaticas para rotas..." + +DIST_DIR="${1:-dist}" +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ ! -d "$DIST_DIR" ]; then + echo -e "${YELLOW}Pasta dist/ nao encontrada. Execute npm run build primeiro.${NC}" + exit 1 +fi + +# Lista de todas as rotas do site +ROUTES=( + "sobre" + "contato" + "cases" + "blog" + "avaliacao" + "tecnologias" + "tecnologia-para-condominios" + "ti-para-contabilidades" + "ti-para-advocacias" + "monitoramento-empresarial" + "firewall-pfsense-empresarial" + "backup-corporativo" + "voip-empresarial" + "servicos/ti-gerenciada" + "servicos/cloud-computing" + "servicos/seguranca" + "servicos/suporte" + "servicos/redes" + "servicos/pabx" + "servicos/backup" + "servicos/servidores" + "servicos/consultoria" + "servicos/noc" + "suporte-windows-server" + "suporte-linux-empresas" + "suporte-pfsense" + "suporte-proxmox" + "suporte-vmware" + "suporte-sharepoint" + "suporte-redes-cisco" + "privacidade" + "termos" +) + +# Copiar index.html como base +cp "$DIST_DIR/index.html" "$DIST_DIR/index.html.bak" + +# Gerar pagina para cada rota +for route in "${ROUTES[@]}"; do + mkdir -p "$DIST_DIR/$route" + cp "$DIST_DIR/index.html" "$DIST_DIR/$route/index.html" +done + +echo -e "${GREEN}OK${NC} ${#ROUTES[@]} rotas estaticas criadas" + +# Criar 404.html (copia do index.html para fallback) +cp "$DIST_DIR/index.html" "$DIST_DIR/404.html" +echo -e "${GREEN}OK${NC} 404.html criado" + +# Limpar backup +rm -f "$DIST_DIR/index.html.bak" + +echo "" +echo -e "${GREEN}Post-build concluido!${NC}" +echo "" +echo "Estrutura gerada:" +echo " dist/index.html (home)" +echo " dist/404.html (pagina nao encontrada)" +echo " dist/{rota}/index.html (${#ROUTES[@]} paginas estaticas)" +echo "" diff --git a/scripts/dev.sh b/scripts/dev.sh index 1fc73d89..e38ae2ab 100644 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -47,6 +47,13 @@ show_banner() { echo "" } +# Post-build: gerar paginas estaticas para rotas +run_post_build() { + local dist_dir="${1:-dist}" + echo -e "${BLUE}[Post-Build]${NC} Gerando paginas estaticas para rotas..." + bash "${SCRIPT_DIR}/build-static-routes.sh" "$dist_dir" +} + # Verificar dependencias check_deps() { if ! command -v node &> /dev/null; then @@ -86,6 +93,10 @@ cmd_build() { echo -e "${BLUE}[2/2]${NC} Configurando robots.txt para producao..." cp "${PROJECT_DIR}/public/robots-producao.txt" "${DIST_DIR}/robots.txt" echo -e "${GREEN}OK${NC} - Build de producao pronto em dist/" + + # Gerar paginas estaticas para rotas + run_post_build "$DIST_DIR" + echo "" echo -e "Para preview: ${CYAN}bash scripts/dev.sh preview${NC}" } @@ -101,6 +112,10 @@ cmd_build_hom() { echo -e "${YELLOW}[2/2]${NC} Configurando anti-indexacao..." # robots.txt ja esta com noindex por padrao echo -e "${GREEN}OK${NC} - Build de homologacao pronto em dist/" + + # Gerar paginas estaticas para rotas + run_post_build "$DIST_DIR" + echo "" echo -e "Para deploy: ${CYAN}bash scripts/dev.sh deploy-hom${NC}" } diff --git a/src/components/SEO.tsx b/src/components/SEO.tsx new file mode 100644 index 00000000..2b7626c8 --- /dev/null +++ b/src/components/SEO.tsx @@ -0,0 +1,120 @@ +// ============================================================================ +// SEO - Meta tags dinamicas por pagina +// ============================================================================ +// Uso: +// +// Cada pagina deve usar este componente com suas proprias tags. +// O canonical e gerado automaticamente a partir da URL atual. +// ============================================================================ + +import { Helmet } from 'react-helmet-async'; +import { useLocation } from 'react-router-dom'; + +interface SEOProps { + title?: string; + description?: string; + keywords?: string; + ogImage?: string; + ogType?: string; + noindex?: boolean; + schema?: Record; +} + +const SITE_URL = 'https://avanzato.com.br'; +const DEFAULT_TITLE = 'Avanzato Tecnologia | TI Gerenciada, Cloud e Seguranca'; +const DEFAULT_DESC = 'Solucoes em TI Gerenciada, Cloud Computing e Seguranca da Informacao. +23 anos de experiencia, +500 clientes atendidos. Suporte 24/7 em Guarulhos e regiao.'; +const DEFAULT_OG_IMAGE = 'https://avanzato.com.br/og-image.jpg'; + +export default function SEO({ + title, + description, + keywords, + ogImage, + ogType = 'website', + noindex = false, + schema, +}: SEOProps) { + const location = useLocation(); + const pathname = location.pathname; + const canonicalUrl = `${SITE_URL}${pathname}`; + const fullTitle = title ? `${title} | Avanzato` : DEFAULT_TITLE; + + return ( + + {/* Title */} + {fullTitle} + + + {/* Description */} + {description && } + {!description && } + + {/* Keywords */} + {keywords && } + + {/* Canonical - DINAMICO por pagina */} + + + {/* Robots */} + {noindex ? ( + + ) : ( + + )} + + {/* Open Graph */} + + + + + + + + + {/* Twitter */} + + + + + + + {/* Schema.org */} + {schema && ( + + )} + + ); +} + +// Schema helpers +export const createServiceSchema = (name: string, description: string, urlPath: string) => ({ + '@context': 'https://schema.org', + '@type': 'Service', + name, + description, + provider: { + '@type': 'Organization', + name: 'Avanzato Tecnologia', + url: SITE_URL, + }, + url: `${SITE_URL}${urlPath}`, + areaServed: { + '@type': 'City', + name: 'Guarulhos', + }, +}); + +export const createFAQSchema = (questions: Array<{q: string; a: string}>) => ({ + '@context': 'https://schema.org', + '@type': 'FAQPage', + mainEntity: questions.map(({q, a}) => ({ + '@type': 'Question', + name: q, + acceptedAnswer: { + '@type': 'Answer', + text: a, + }, + })), +}); diff --git a/src/main.tsx b/src/main.tsx index bef5202a..0a361e56 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,13 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { HelmetProvider } from 'react-helmet-async' import './index.css' import App from './App.tsx' createRoot(document.getElementById('root')!).render( - + + + , ) diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index aedf2a22..5d399975 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -1,3 +1,4 @@ +import SEO from '../components/SEO'; import { useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import { gsap } from 'gsap'; @@ -132,6 +133,10 @@ const Blog = () => { return (
+ {/* Hero */}
diff --git a/src/pages/Cases.tsx b/src/pages/Cases.tsx index 6fd4405b..f92d21a8 100644 --- a/src/pages/Cases.tsx +++ b/src/pages/Cases.tsx @@ -1,3 +1,4 @@ +import SEO from '../components/SEO'; import { useEffect, useRef } from 'react'; import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; @@ -199,6 +200,10 @@ const Cases = () => { return (
+ {/* Schema.org JSON-LD */}