廣州白云做網(wǎng)站濟(jì)寧百度推廣電話
文章目錄
- 前言
- 網(wǎng)站默認(rèn)語(yǔ)言問(wèn)題
- 網(wǎng)站訪問(wèn)統(tǒng)計(jì)問(wèn)題
- Error: Empty components are self-closing
- Error: A space is required before closing bracket
- 總結(jié)
前言
看標(biāo)題大概能猜到這是一篇雜合體的總結(jié),是這兩天處理網(wǎng)站遇到的小問(wèn)題,怕過(guò)段時(shí)間再忘了所以總結(jié)到一起便于反過(guò)來(lái)查找問(wèn)題解決方案,這個(gè)問(wèn)題都是之前未曾接觸過(guò)的,但遇到他們不用懼怕,只要假裝自己能解決就行,這樣穩(wěn)扎穩(wěn)打的找方案就能解決了。
網(wǎng)站默認(rèn)語(yǔ)言問(wèn)題
一個(gè)使用Nextjs框架編寫(xiě)的網(wǎng)站,支持中日英三種語(yǔ)言,如果訪問(wèn)主頁(yè)面時(shí)未選擇語(yǔ)言,則按照瀏覽器的語(yǔ)言來(lái)選擇,這是合理的,一個(gè)人如果總用中文的瀏覽器,那么他在訪問(wèn)一個(gè)新網(wǎng)站時(shí)較大可能是想看中文的頁(yè)面,所以我們優(yōu)先選擇當(dāng)前瀏覽器的語(yǔ)言即可,默認(rèn)跳轉(zhuǎn)到指定語(yǔ)言的頁(yè)面,選擇語(yǔ)言的代碼如下,是一個(gè)中間件 middleware.ts
,問(wèn)題在于默認(rèn)語(yǔ)言為日文的瀏覽器,打開(kāi)網(wǎng)頁(yè)是英文的,這就得查查是因?yàn)槭裁戳?#xff1a;
import { NextRequest, NextResponse } from 'next/server';
import acceptLanguage from 'accept-language';
import { fallbackLng, languages } from './app/i18n/settings';acceptLanguage.languages(languages);export const config = {// matcher: '/:lng*'matcher: ['/((?!api|_next/static|_next/image|images|svg|assets|favicon.ico|sw.js).*)']
};const cookieName = 'i18next';export function middleware(req: NextRequest) {let lng;// if (req.cookies.has(cookieName)) lng = acceptLanguage.get(req.cookies.get(cookieName)?.value);if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language'));if (!lng) lng = fallbackLng;// Redirect if lng in path is not supportedif (!languages.some((loc) => req.nextUrl.pathname.startsWith(`/${loc}`))&& !req.nextUrl.pathname.startsWith('/_next')) {return NextResponse.redirect(new URL(`/${lng}${req.nextUrl.pathname}`, req.url));}if (req.headers.has('referer')) {const refererUrl = new URL(req.headers.get('referer') || 'zh');const lngInReferer = languages.find((l) => refererUrl.pathname.startsWith(`/${l}`));const response = NextResponse.next();if (lngInReferer) response.cookies.set(cookieName, lngInReferer);return response;}return NextResponse.next();
}
其中 matcher
是用來(lái)匹配路徑的,這個(gè)配置表示,中間件會(huì)在請(qǐng)求的路徑不包含特定字符串時(shí)觸發(fā)。也就是說(shuō),路徑不能包含指定的 api
、_next/static
等詞綴。
網(wǎng)站語(yǔ)言看這一句 if (!lng) lng = acceptLanguage.get(req.headers.get('Accept-Language'));
就行了,它的含義是網(wǎng)站可以根據(jù) Accept-Language 請(qǐng)求頭來(lái)檢測(cè)瀏覽器的首選語(yǔ)言,并返回相應(yīng)語(yǔ)言的內(nèi)容。
而我將瀏覽器默認(rèn)語(yǔ)言設(shè)置為日語(yǔ)時(shí),打開(kāi)網(wǎng)站傳遞的 Accept-Language
內(nèi)容為 ja,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
根據(jù)你提供的 Accept-Language
內(nèi)容 ja,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
,以下是中間件的處理過(guò)程:
acceptLanguage.get(req.headers.get('Accept-Language'))
函數(shù)會(huì)根據(jù) Accept-Language
頭部的內(nèi)容來(lái)選擇最適合的語(yǔ)言。根據(jù)我瀏覽器傳遞的內(nèi)容:
ja
(日語(yǔ))有最高的優(yōu)先級(jí)。en
(英語(yǔ))優(yōu)先級(jí)為 0.9。zh-CN
(簡(jiǎn)體中文)優(yōu)先級(jí)為 0.8。zh
(中文)優(yōu)先級(jí)為 0.7。
由于 ja
的優(yōu)先級(jí)是 1(默認(rèn))并且是最優(yōu)的匹配,所以 acceptLanguage.get()
會(huì)選擇 ja
作為首選語(yǔ)言。
但是可以還取決于 languages
的值,因?yàn)?acceptLanguage
是通過(guò) languages
完成初始化的,而我查詢了項(xiàng)目中它的值為 export const languages = ['jp', 'en', 'zh'];
,發(fā)現(xiàn)問(wèn)題了沒(méi)有?日語(yǔ)瀏覽器將 ja
作為最優(yōu)先展示語(yǔ)言,但是備選項(xiàng)里沒(méi)有 ja
,所以最終選出的結(jié)果是 en
,這也就解釋了為什么日語(yǔ)瀏覽器默認(rèn)打開(kāi)網(wǎng)頁(yè)時(shí)顯示英文頁(yè)面的現(xiàn)象了。
日文的縮寫(xiě)通常是 “ja”,而不是 “jp”。
- “ja” 是 ISO 639-1 標(biāo)準(zhǔn)中日語(yǔ)的語(yǔ)言代碼。
- “jp” 通常用來(lái)表示 日本(Japan)的國(guó)家代碼,在 ISO 3166-1 標(biāo)準(zhǔn)中使用。
因此,在國(guó)際化(i18n)和多語(yǔ)言網(wǎng)站的語(yǔ)言代碼中,日文通常使用 “ja”。
只要原因后就好修改了,改成ja也好,保持jp也罷,修正邏輯按照一致的順序處理,問(wèn)題也就能解決了。
網(wǎng)站訪問(wèn)統(tǒng)計(jì)問(wèn)題
如果是一個(gè)網(wǎng)站開(kāi)發(fā)者遇到這種問(wèn)題,應(yīng)該會(huì)有一個(gè)完整的解決方案,而我這種半吊子的網(wǎng)頁(yè)開(kāi)發(fā)人員,只想盡快的解決問(wèn)題,所以我的搜索方向就是怎么盡快解決這個(gè)問(wèn)題。起初搜到的方案是使用 Vercel Analytics
來(lái)查看,初始有個(gè)免費(fèi)額度,超過(guò)25,000 events要進(jìn)行收費(fèi),因?yàn)槲业木W(wǎng)站就部署在 Vercel 上,所以這種方案應(yīng)該是最融洽的,查看每月的 events 使用量估算了費(fèi)用,每月要 $100 以上,糾結(jié)中。
然后就搜到了 Google Analytics
,介紹里說(shuō)它完全免費(fèi),應(yīng)用廣泛,但是最近評(píng)價(jià)卻不好,一方面是指責(zé)它收集隱私信息,另一方面是說(shuō)它被很多瀏覽器插件屏蔽,導(dǎo)致無(wú)法準(zhǔn)確統(tǒng)計(jì),在抵制它的同時(shí)很多人都推薦使用了 Umami
。
Umami
是一個(gè)開(kāi)源的簡(jiǎn)單、隱私友好的網(wǎng)站分析工具。它提供了類(lèi)似于 Google Analytics 的功能,但更加專注于數(shù)據(jù)隱私,且無(wú)需依賴第三方 cookies。這使得 Umami 成為那些希望追蹤用戶活動(dòng)但又需要遵守嚴(yán)格隱私法律(如 GDPR 或 CCPA)的個(gè)人和企業(yè)的理想選擇。
Umami
是完全開(kāi)源的(使用 MIT 許可證),代碼可以在 GitHub 上找到并自行部署??蛻舳四_本文件僅約 2 KB,加載速度快,不會(huì)影響網(wǎng)頁(yè)性能。時(shí)查看訪問(wèn)者活動(dòng),例如當(dāng)前在線人數(shù)、瀏覽器、設(shè)備、地域等信息。
后端:Node.js 和 PostgreSQL。前端:基于 React 構(gòu)建。部署:支持 Docker,配置簡(jiǎn)單。技術(shù)棧提起來(lái)挺簡(jiǎn)單,但是像我這么懶的人現(xiàn)在還不打算配置一遍,自己配置還要買(mǎi)服務(wù)器和數(shù)據(jù)庫(kù),因?yàn)槲也榈剿幸粋€(gè) Umami Cloud
。
Umami Cloud 是 Umami
的云托管服務(wù),提供了一個(gè)簡(jiǎn)單、隱私友好的網(wǎng)站分析工具的在線版本。它允許用戶通過(guò) Umami 提供的 Web 界面,無(wú)需自行托管和管理服務(wù)器即可使用該分析工具。相比自己搭建 Umami,使用 Umami Cloud 可以節(jié)省大量的部署和維護(hù)成本,適合不想處理服務(wù)器配置的用戶。
用戶無(wú)需擔(dān)心部署和維護(hù)服務(wù)器,Umami Cloud 提供了一個(gè)即開(kāi)即用的服務(wù),只需要注冊(cè)并配置網(wǎng)站即可開(kāi)始使用。與自托管的 Umami 一樣,Umami Cloud 注重用戶隱私,不收集個(gè)人可識(shí)別信息。它也不依賴 cookies 或第三方追蹤,因此完全符合 GDPR 和其他隱私法規(guī)。
用戶只需要將提供的 JavaScript 跟蹤代碼插入到網(wǎng)站頁(yè)面,不需要任何后端代碼,適合非技術(shù)用戶。Umami Cloud 提供了不同的定價(jià)套餐,包括免費(fèi)和付費(fèi)版本。免費(fèi)版本適合小型網(wǎng)站或個(gè)人使用,付費(fèi)版本提供更多的功能和數(shù)據(jù)存儲(chǔ)選項(xiàng)。
看起來(lái)數(shù)據(jù)大了還是得付錢(qián),免費(fèi)版本限制 100K events per month,Up to 3 websites,6 month data retention,對(duì)于我的網(wǎng)站來(lái)說(shuō)還是不夠用,但付費(fèi)的價(jià)格相比Vercel算是降了不少,所以我把它的 跟蹤代碼 胡亂的集成到了我項(xiàng)目的 <head>...</head>
中,進(jìn)而引發(fā)了后面的問(wèn)題
<script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID"></script>
Error: Empty components are self-closing
這個(gè)錯(cuò)誤的完整顯示如下:
Failed to compile.
30:13 Error: Empty components are self-closing react/self-closing-comp
info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rulesELIFECYCLE? Command failed with exit code 1.
Error: Command "pnpm run build" exited with 1
我的代碼開(kāi)始寫(xiě)成了這樣:
export default function RootLayout({children,params: { lng },
}: {children: React.ReactNode;params: { lng: LanguageType };
}) {return (<html lang={lng} className={myFont.variable}><head><script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID"></script></head><body className="relative font-my">{children}</body></html>);
}
看著ChatGPT的解釋我陷入了沉思,不過(guò)后來(lái)我明白了它的意思,這個(gè)錯(cuò)誤是由于 ESLint 中的 react/self-closing-comp
規(guī)則引起的,該規(guī)則要求空的組件標(biāo)簽必須是自閉合的。
解決方法是檢查報(bào)錯(cuò)所在的代碼(通常是在第 30 行第 13 列),很可能是你有一個(gè)空的元素,例如:
<div></div>
它應(yīng)該改成自閉合的寫(xiě)法:
<div />
所以我把代碼改成了這樣 <head><script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID"/></head>
,去掉了 </script>
然后就報(bào)了下面這個(gè)錯(cuò)。
Error: A space is required before closing bracket
完整錯(cuò)誤如下:
Failed to compile.
30:120 Error: A space is required before closing bracket react/jsx-tag-spacing
info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rulesELIFECYCLE? Command failed with exit code 1.
Error: Command "pnpm run build" exited with 1
這次我還是沒(méi)反應(yīng)過(guò)來(lái),經(jīng)過(guò)ChatGPT解釋發(fā)現(xiàn),之前就已經(jīng)給出準(zhǔn)備的例子了,這個(gè)新的錯(cuò)誤是由 ESLint 的 react/jsx-tag-spacing 規(guī)則引起的,它要求在自閉合標(biāo)簽的閉合括號(hào)前留一個(gè)空格。所以最終改成下面這樣就沒(méi)問(wèn)題了
<head><script defer src="https://cloud.umami.is/script.js" data-website-id="YOUR_WEBSITE_ID" /></head>
真是一個(gè)空格也不能錯(cuò)啊~
總結(jié)
- 網(wǎng)站的默認(rèn)語(yǔ)言可以根據(jù)瀏覽器的設(shè)置語(yǔ)言來(lái)初始化,獲取代碼簡(jiǎn)化為
acceptLanguage.get(req.headers.get('Accept-Language'));
- 網(wǎng)頁(yè)統(tǒng)計(jì)可以使用
Vercel Analytics
、Google Analytics
或者Umami
Vercel Analytics
與 Vercel 兼容性好,畢竟出自同一家班底,配合會(huì)比較順暢,應(yīng)該還會(huì)有自家產(chǎn)品的關(guān)聯(lián)優(yōu)化Google Analytics
依賴 cookies,可能涉及隱私問(wèn)題,數(shù)據(jù)存儲(chǔ)在 Google 的服務(wù)器,由 Google 管理,功能全面,適合復(fù)雜需求Umami
完全隱私友好,無(wú) cookies,核心功能足夠,但比 Google Analytics 少,可以用戶自行托管,完全掌控
他日若是同淋雪,也算此生共白頭。在兩個(gè)人維系的關(guān)系中,一方的忍耐退讓并不會(huì)讓另一方生出善意,反而會(huì)導(dǎo)致對(duì)方以為之前的所有都是理所應(yīng)當(dāng),進(jìn)而變本加厲。總會(huì)有兩個(gè)人不在一個(gè)頻道上,你給她講理,她跟你講情,你跟她講情,她跟你講法,你跟她講法,他跟你講理,反正你永遠(yuǎn)對(duì)不了~