2026年8月10日 星期一

for nex.js :什麼時候該「rm -rf .next」

 有時明明之前build的結果執行很正確
但改了設定就會出現404錯誤
這時就是砍快取的時候了
是什麼時候會用到這招(rm -rf .next) ? :

1.新建的路由檔案即使路徑正確也出現 404 錯誤——像是修改 .env.local設定中的 「NEXT_PUBLIC_BASE_PATH」 後,Turbopack 的路由清單有時會遺漏新建立的嵌套動態資料夾。

2.更改後next.config.tsbasePath. output、、和serverExternalPackages都會影響構建樹的佈局方式,並且舊配置的過時產物會殘留下來。

3.在移動或重新命名項目資料夾之後—.next/standalone它包含已解析的路徑。

總之,當症狀與你正在閱讀的程式碼完全不符時,如果檔案明顯正確但行為不符,那麼在懷疑自己之前,首先應該懷疑快取問題。

以上,一起加油吧!! 


2026年7月8日 星期三

for next.js:什麼時候用req:Request ,什麼時候用req:NextRequest ?

 不仔細想的話,直接全部都用「req:NextRequest」,
因為 NextRequest extends Request

說明:
-------------------

// NextRequest EXTENDS Request:
// = Request + extra Next.js features
req.nextUrl          // ← parsed URL object (convenient!)
req.nextUrl.searchParams  // ← query params directly
req.nextUrl.pathname      // ← /api/books
req.geo              // ← geo location (Edge only)
req.ip               // ← client IP
req.cookies          // ← cookies helper
  .get('name')
  .set('name', value)
  .delete('name')

-------------------
用Request的時機
-------------

//有query string傳真的時候
export async function GET(req: Request) {

  const url = new URL(req.url)

  const param = url.searchParams.get('keyword')

  // ← manual URL parsing needed

}


export async function POST(req: Request) {

  const body = await req.json()  // ← same on both

  ...

}

export async function POST(req: Request) {

  const { userId, password } = await req.json()

}

-------------
用NextRequest的時候--有query string傳參的時間
--------------

export async function GET(req: NextRequest) {

  const keyword = req.nextUrl.searchParams.get('keyword')

}


export async function POST(req: NextRequest) {

  const body = await req.json()  // ← works perfectly ✓

  const { title, author } = body

}

--------------
以上

2026年6月22日 星期一

為什麼在寫Next.js route中,參數要寫做「{params}....」而不是直接寫做params,背後是有什麼機制?

 如果你有在寫Next.js的api route.ts的話,會常看到類似以下的片段
------------

export async function GET(

  req: Request,

  {params}: { params: Promise<{ batchId: string }> }

) {

  const { batchId } = await params
...
....
...

}

------------
為什麼是寫作「{params}」而非是「params」就好?
事實上,那是簡寫,真正的code是
----
export async function GET(
  req: Request,
  context: { params: Promise<{ batchId: string }> }
) {
  const { batchId } = await context.params
...
...
}

----
那問題來了,context是什麼,route到底會收到什麼參數?
routing從url的path解析出[...]/[...]/ 對應到相對的route.ts及方法後(以GET為例),參數內容如下
-------------
GET(req, context)
  │         │
  │         └── Route context (URL structure)
  │               context.params  → path segments [batchId],
          
                                               [sessionId]
  │               context.searchParams →
  │               query string (?tab=x) [page routes only]
  │
  └── Request object (HTTP request details)
req.method   → GET / POST / DELETE
req.headers  → Authorization, Content-Type, cookies...
req.body     → POST/PUT body (JSON payload)
req.url      → full URL string
-------------
就是這樣的內容,AI會直接把context......寫成{params} 的簡寫方式
A:「那,如果後面帶『?key=value』的方式如何取值?」
Q:就要從req.url轉換出「searchParams」了
--------------
  const { searchParams } = new URL(req.url);

  // Example:
  // /api/user?id=1&name=john&role=admin
  const id = searchParams.get("id");
  const name = searchParams.get("name");
  const role = searchParams.get("role");

上述是在route.ts裡的寫法

而在tsx裡的寫法卻又是不一樣了

-----如同之前context.searchParams → query string (?tab=x) [page routes only]所示-------
// Page components (page.tsx) ONLY:
export default function Page({
  params,
  searchParams,  // ← available here as context prop
}: {
  params: Promise<{ batchId: string }>
  searchParams: Promise<{ tab: string }>
}) {
  const { batchId } = use(params)
  const { tab } = use(searchParams)  // ← works in page.tsx
   ...
   .....
   ...
}
------------
 ts & tsx 寫法不同,千萬要注意
(好像對AI generated code如此講究的工程師也不多了吧.現在都是直接測了能跑交件了)




2026年5月19日 星期二

for next.js 16,開發環境中:為什麼訪問api可以,但訪問「/」就整台機器給你掛掉

為了怕日後又重複遇到這個現象,所以做這筆記... 

在學習專案中先以一個route.ts測試db部分
------src/app/api/test/route.ts------------

import db from '@/lib/db';
import { NextResponse } from 'next/server';
export async function GET(){
    try{
         const result=await db('books').count(' * as total').first();
         return NextResponse.json({
                success:true,
                totalBooks:result?.total
            });
    }catch(error){
            return NextResponse.json({
                    success:false,
                    error:String(error)
                },{
                    status:500
                });
    }
}

--------------------------------------------
在terminal用"npm run dev" 起來要查看個api  (url : http://localhost3000/api/test)時
系統會出現一堆package沒安裝的訊息,這時,別傻傻地一個一個安裝(因為knex 依賴的套件如果沒有特別指定.會是所有的db都來一份的)
此時請到「next.config.ts」設定,因為我們這次用了knex操作postgresql,所以在serverExternalPackages中,加上「knex、pg」套件
--------------

import type { NextConfig } from "next";
const nextConfig: NextConfig = {
  /* config options here */
 serverExternalPackages: ['knex', 'pg', 'jsonwebtoken', 'bcryptjs'],
};
export default nextConfig;

---------------

總該可以測了吧,是的,你可以看到api如期工作








現在,我們要回去幹正事了--回頭查看一下localhot:3000/
結果terminal一直在compiling....






然後系統就整個給你關機罷工去了!!! (連個error都不response.....)
雖然說「佛跳牆弄到爆炸是不允許的,應該要踢你出局,不過念在你有創意夠噱頭,還有2 分鐘再做一個吧!」,架構再怎麼好,會雄雄讓人怎麼死的都不知道是不行的

為什麼會這樣!?
因為最初那個test的route.ts比較小,一下子就compile  (webpack)完成可用了
但是request「/」時,"如果沒有指定的話"是整個網站都compile
「那...我只想要compile需要的頁面的話,要如何指定?」
請到「package.json」做以下的修改
----





----

即,把script--> "dev":"next dev "改為"dev":"next dev --turbo"
存檔,就可以解決這個問題了

一起加油吧

2026年5月4日 星期一

沒事把node.js簽發的jwt拿來呼叫.net core web API時踩了一堆坑的故事

 照理說dot net 的 web api與nextjs整合應該是前端用Browser -->next.js --> .net web api (讓dot net的microservice放在另一個網段)
不過如果直接讓node.js簽發給browser client的jwt拿來call .net web api會發生什麼事 (為什麼要這樣做?不為什麼,就只是想知道罷了)

這一路走來我遇到了狀況,因為很有趣,所以把它做個記錄.

狀況一.401錯誤--起源是錯用了AOT,而不是傳統的Web API專案
原因: VS 2026 建專案時選到了 "Web API (Native AOT)" 模板,使用 CreateSlimBuilder + PublishAot。AOT 的 trimmer 會把 JWT 需要的 crypto provider 砍掉。



所以最初在設計Web API時,一定要選對專案型態 ,不是AOT,而是最上面那種傳統Web API型態的專案
AOT與一般web API專案的差別:

                    Regular              AOT
─────────────────────────────────────────────────
Builder           CreateBuilder        CreateSlimBuilder
編譯方式           IL → JIT             直接編譯成機器碼
啟動速度           ~500ms+              ~50ms
發布大小           需要 Runtime          單一檔案 (~10MB)
Reflection         ✅ 完整支援           ❌ 受限
JSON 序列化        System.Text.Json      需要 Source Generator
JWT/Crypto         ✅ 全部可用           ⚠️ 可能被 Trim 掉
NuGet 相容性       幾乎全部              部分不支援
適用場景           一般 API、企業應用     微服務、Serverless

-----------------------------------------------------------------------
(小的沒在用AOT,如有先進有補充建議的亦請不吝賜教)

重新增加一個web api project,修改program.cs
-----------------------------

...
...
var
key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"]) );
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = key, ValidateIssuer = false, ValidateAudience = false, }; });
....
...
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
-----------------------------
Action in Sample controller
-------------------------
[Authorize]
[HttpGet]
public IActionResult Get() {
    var username = User.FindFirst("username")?.Value;
    var role = User.FindFirst("role")?.Value;
    return Ok();
}
----------------------------
之後....
狀況二.錯誤訊息 IDX10517: Signature validation failed. The token's kid is missing.
------------------------------------------
原因: .NET 10 的 Microsoft.IdentityModel 套件收緊了驗證規則,要求 token header 中必須有 kid(Key ID)。而 Node.js 的 jose 預設不產生 kid
解法:
這要在nextjs 簽發JWT及解析端(.net core web API)都要再加一個kid
------JWT.ts-----------
export async function signJWT(user:AuthUser):Promise<string> {
    return new SignJWT(
       {
        id:user.id,
        username:user.username,
        role:user.role
       }
    ).setProtectedHeader({alg:'HS256') <--Before
    ).setProtectedHeader({alg:'HS256', kid: 'MyKidKey'}) <--after.
    .setIssuedAt()
    .setExpirationTime('8h')
    .sign(SECRET); <---SECRET是從.node 設定檔來的,跟program.cs的 Configuration["JWT_SECRET"]相呼應
}
------Program.cs------
var key = new SymmetricSecurityKey(
    Encoding.UTF8.GetBytes(Configuration["JWT_SECRET"])
);
key.KeyId = "MyKidKey";  <----KID set here.
--------------------------
這下子,總該可以了吧?
再次用postman設定好bearer token後,試著request controller 時
出現新的錯誤錯誤--
狀況三:「The signature is invalid」錯誤
這東西真的是超難解的,最後是把兩邊的code都送給AI去看的結果才知是「長度」問題:

原因: HS256 要求 key 至少 256 bits(32 bytes)。原本的 secret 只有 27 字元,jose 和 .NET 對不足長度的 key 可能有不同的 padding 行為。

解法: Secret 改成 32 字元以上,兩邊同步更新。

最後,終於在controller的GET action中,可以讀取到User資訊了
但是「User.FindFirst("role")?.Value」回傳null,這樣還是不完成,
改成「User.FindFirst(ClaimTypes.Role)?.Value」,就會得到role值了

別忘了,在[Authorize...]指定需要的角色權限.ex  [Authorize(Roles = "manager,admin")]

有些知識真的是靠踩坑得來的,不是AI直接給你就會了解的.