Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
VCCI-News
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Văn Hoàng
VCCI-News
Commits
f6cd146b
Commit
f6cd146b
authored
Sep 10, 2026
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: SEO
parent
63303609
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
187 additions
and
12 deletions
+187
-12
page.tsx
src/app/(main)/(home)/page.tsx
+6
-5
layout.tsx
src/app/admin/layout.tsx
+2
-2
layout.tsx
src/app/layout.tsx
+1
-5
robots.ts
src/app/robots.ts
+18
-0
sitemap.ts
src/app/sitemap.ts
+160
-0
No files found.
src/app/(main)/(home)/page.tsx
View file @
f6cd146b
'use client'
import
FeaturedNews
from
"./components/featured-news"
;
import
Advertisements
from
"./components/quick-links"
;
import
HorizontalAdBanner
from
"./components/horizontal-ad-banner"
;
...
...
@@ -9,12 +7,15 @@ import BusinessOpportunities from "./components/business-opportunities";
import
PolicyAndLaws
from
"./components/policies-and-laws"
;
import
EventsCalendar
from
"./components/events-calendar"
;
import
Banner
from
"./components/banner"
;
import
Members
from
'./components/members'
;
import
VideoAndPartners
from
'./components/video-and-patners'
;
import
Members
from
"./components/members"
;
import
VideoAndPartners
from
"./components/video-and-patners"
;
export
default
function
HomePage
()
{
return
(
<
div
>
<
h1
className=
"sr-only"
>
Liên đoàn Thương mại và Công nghiệp Việt Nam, CN TP.HCM
</
h1
>
<
Banner
/>
{
/* contents */
}
<
div
className=
"container mx-auto px-3 sm:px-6 lg:px-10 space-y-6"
>
...
...
@@ -49,4 +50,4 @@ export default function HomePage() {
</
div
>
</
div
>
);
};
\ No newline at end of file
}
src/app/admin/layout.tsx
View file @
f6cd146b
'use client'
;
import
React
from
'react'
;
import
{
usePathname
}
from
'next/navigation'
;
import
{
AdminAuthLoadingScreen
,
...
...
@@ -10,11 +9,12 @@ import { AdminSidebar } from '@/components/layout/admin/admin-sidebar';
import
{
AdminHeader
}
from
'@/components/layout/admin/admin-header'
;
import
{
useSidebarStore
}
from
'@/hooks/use-admin-sidebar'
;
import
{
cn
}
from
'@/lib/utils'
;
import
{
useEffect
}
from
'react'
;
function
AdminShell
({
children
}:
{
children
:
React
.
ReactNode
})
{
const
{
close
,
isOpen
}
=
useSidebarStore
();
React
.
useEffect
(()
=>
{
useEffect
(()
=>
{
const
mediaQuery
=
window
.
matchMedia
(
'(max-width: 1023px)'
);
const
syncSidebar
=
()
=>
{
if
(
mediaQuery
.
matches
)
close
();
...
...
src/app/layout.tsx
View file @
f6cd146b
...
...
@@ -30,11 +30,7 @@ export const metadata: Metadata = {
robots
:
{
index
:
true
,
follow
:
true
,
googleBot
:
{
index
:
true
,
follow
:
true
}
}
};
export
default
function
RootLayout
({
children
,
}:
Readonly
<
{
children
:
React
.
ReactNode
;
}
>
)
{
export
default
function
RootLayout
({
children
}:
Readonly
<
{
children
:
React
.
ReactNode
}
>
)
{
return
(
<
html
lang=
"en"
>
<
body
>
...
...
src/app/robots.ts
0 → 100644
View file @
f6cd146b
import
type
{
MetadataRoute
}
from
"next"
;
import
links
from
"@links/index"
;
const
SITE_URL
=
links
.
siteURL
.
replace
(
/
\/
+$/
,
""
);
export
default
function
robots
():
MetadataRoute
.
Robots
{
return
{
rules
:
[
{
userAgent
:
"*"
,
allow
:
"/"
,
disallow
:
[
"/admin"
,
"/api"
],
},
],
sitemap
:
`
${
SITE_URL
}
/sitemap.xml`
,
host
:
SITE_URL
,
};
}
src/app/sitemap.ts
0 → 100644
View file @
f6cd146b
import
type
{
MetadataRoute
}
from
"next"
;
import
links
from
"@links/index"
;
import
{
getApiV10Category
}
from
"@/api/vcci-news/endpoints/category"
;
import
{
getApiV10Post
}
from
"@/api/vcci-news/endpoints/post"
;
const
SITE_URL
=
links
.
siteURL
.
replace
(
/
\/
+$/
,
""
);
const
STATIC_PAGE_SLUGS
=
[
"an-pham"
,
"thu-vien-tai-lieu"
,
"ve-vcci-hcm"
,
"dich-vu-cung-cap"
,
"dang-ky-hoi-vien"
,
"ho-so-thi-truong"
,
"loi-ich-hoi-vien-vcci"
,
"phap-che"
,
"giay-chung-nhan-gcn-va-chung-tu-thuong-mai-cttm"
,
"quy-trinh-tiep-nhan-ho-so-cap-gcn-va-xac-nhan-cttm"
,
"bieu-mau-gcn-va-noi-dung-khai-bao-gcn-cttm"
,
"phi-cap-gcn-va-xac-nhan-cttm"
,
"diem-cap-va-thoi-gian-cap-gcn-va-xac-nhan-cttm"
,
"thong-tin-lien-he"
,
"danh-ba-hoi-vien"
,
"site-map"
,
"video"
,
];
const
normalizePath
=
(
value
?:
string
|
null
)
=>
{
const
trimmed
=
value
?.
trim
()
??
""
;
if
(
!
trimmed
||
trimmed
===
"/"
)
return
"/"
;
return
`/
${
trimmed
.
replace
(
/^
\/
+|
\/
+$/g
,
""
)}
`
;
};
const
toIso
=
(
value
?:
string
|
null
)
=>
{
if
(
!
value
)
return
undefined
;
const
date
=
new
Date
(
value
);
return
Number
.
isNaN
(
date
.
getTime
())
?
undefined
:
date
.
toISOString
();
};
type
CategoryRow
=
{
id
:
string
;
name
?:
string
|
null
;
slug
?:
string
|
null
;
url
?:
string
|
null
;
type
?:
string
|
null
;
updated_at
?:
string
|
null
;
};
type
PostRow
=
{
id
?:
string
|
null
;
slug
?:
string
|
null
;
title
?:
string
|
null
;
published_at
?:
string
|
null
;
release_at
?:
string
|
null
;
updated_at
?:
string
|
null
;
};
type
PagedRows
<
T
>
=
{
responseData
?:
{
count
?:
number
;
rows
?:
T
[];
};
};
const
fetchAllNewsPosts
=
async
():
Promise
<
PostRow
[]
>
=>
{
const
pageSize
=
100
;
const
maxPages
=
50
;
const
all
:
PostRow
[]
=
[];
for
(
let
page
=
1
;
page
<=
maxPages
;
page
++
)
{
try
{
const
response
=
(
await
getApiV10Post
({
page
,
pageSize
,
sortField
:
"release_at"
,
sortOrder
:
"desc"
,
filters
:
"is_hidden==false,is_active==true,type==news"
,
}))
as
unknown
as
PagedRows
<
PostRow
>
;
const
rows
=
response
?.
responseData
?.
rows
??
[];
all
.
push
(...
rows
);
const
count
=
Number
(
response
?.
responseData
?.
count
??
0
);
if
(
rows
.
length
<
pageSize
||
all
.
length
>=
count
)
break
;
}
catch
{
break
;
}
}
return
all
;
};
export
default
async
function
sitemap
():
Promise
<
MetadataRoute
.
Sitemap
>
{
const
now
=
new
Date
().
toISOString
();
const
entries
:
MetadataRoute
.
Sitemap
=
[
{
url
:
SITE_URL
,
lastModified
:
now
,
changeFrequency
:
"daily"
,
priority
:
1
,
},
];
// Trang tĩnh
for
(
const
slug
of
STATIC_PAGE_SLUGS
)
{
entries
.
push
({
url
:
`
${
SITE_URL
}
/
${
slug
}
`
,
lastModified
:
now
,
changeFrequency
:
"monthly"
,
priority
:
0.6
,
});
}
// Danh mục + bài viết (chạy song song)
const
[
categoryResponse
,
posts
]
=
await
Promise
.
all
([
getApiV10Category
({
page
:
1
,
pageSize
:
200
,
sortField
:
"sort_order"
,
sortOrder
:
"asc"
,
}).
catch
(()
=>
null
),
fetchAllNewsPosts
(),
]);
const
categoryRows
=
((
categoryResponse
as
unknown
as
PagedRows
<
CategoryRow
>
|
null
)?.
responseData
?.
rows
??
[])
as
CategoryRow
[];
const
seenCategoryPaths
=
new
Set
<
string
>
();
for
(
const
cat
of
categoryRows
)
{
if
(
!
cat
.
id
||
!
cat
.
url
)
continue
;
const
path
=
normalizePath
(
cat
.
url
);
if
(
path
===
"/"
||
seenCategoryPaths
.
has
(
path
))
continue
;
seenCategoryPaths
.
add
(
path
);
entries
.
push
({
url
:
`
${
SITE_URL
}${
path
}
`
,
lastModified
:
toIso
(
cat
.
updated_at
)
??
now
,
changeFrequency
:
"daily"
,
priority
:
0.8
,
});
}
const
seenPostSlugs
=
new
Set
<
string
>
();
for
(
const
post
of
posts
)
{
const
slug
=
post
.
slug
?.
trim
();
if
(
!
slug
||
!
post
.
id
||
!
post
.
title
)
continue
;
if
(
seenPostSlugs
.
has
(
slug
))
continue
;
seenPostSlugs
.
add
(
slug
);
entries
.
push
({
url
:
`
${
SITE_URL
}
/
${
slug
}
`
,
lastModified
:
toIso
(
post
.
updated_at
)
??
toIso
(
post
.
published_at
)
??
toIso
(
post
.
release_at
)
??
now
,
changeFrequency
:
"weekly"
,
priority
:
0.7
,
});
}
return
entries
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment